[
  {
    "path": ".codecov.yml",
    "content": "codecov:\n  notify:\n    require_ci_to_pass: yes\n    after_n_builds: 18\n  strict_yaml_branch: main  # only use the latest copy on the main branch\n\nignore:\n  - \"**/*.pb.go\"\n  - \"**/mocks/*\"\n  - \"proto-gen/*/*\"\n  - \"thrift-gen/*/*\"\n  - \"**/main.go\"\n  - \"examples/hotrod\"\n  - \"internal/storage/integration\"\n  - \"cmd/jaeger/internal/integration\"\n  - \"internal/tools\"\n\ncoverage:\n  precision: 2\n  round: down\n  range: \"95...100\"\n  status:\n    project:\n      default:\n        enabled: yes\n        target: 95%\n    patch:\n      default:\n        enabled: yes\n        target: 95%\n"
  },
  {
    "path": ".fossa.yml",
    "content": "# FOSSA Configuration File\n# https://github.com/fossas/fossa-cli/blob/master/docs/references/files/fossa-yml.md\n#\n# This configuration excludes development-only tooling from license scanning.\n# The excluded paths contain GPL-3.0 licensed tools (golangci-lint and its plugins)\n# that are used only during development and are NOT compiled into or distributed\n# with the final Jaeger binaries.\n#\n# These tools are in separate Go modules specifically to isolate them from\n# production dependencies. Using GPL-licensed development tools to build\n# Apache 2.0 licensed software does not create any license compliance issues.\n\nversion: 3\n\nproject:\n  id: git+github.com/jaegertracing/jaeger\n  name: jaeger\n\npaths:\n  exclude:\n    # Development tooling (golangci-lint and plugins) - GPL-3.0 licensed\n    # These are NOT part of the distributed binaries\n    - ./internal/tools\n\n    # Debug build utilities - only used for debugging, not distributed\n    - ./scripts/build/docker/debug\n\n    # Git submodules - scanned separately in their own repositories\n    - ./idl\n    - ./jaeger-ui\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "\n* @jaegertracing/jaeger-maintainers\n"
  },
  {
    "path": ".github/actions/block-pr-from-main-branch/action.yml",
    "content": "name: 'block-pr-not-on-main'\ndescription: 'Blocks PRs from main branch of forked repository'\nruns:\n  using: \"composite\"\n  steps:\n    - name: Ensure PR is not on main branch\n      shell: bash\n      run: |\n        echo \"Repo: ${{ github.repository }}\"\n        echo \"Head Repo: ${{ github.event.pull_request.head.repo.full_name }}\"\n        echo \"Forked: ${{ github.event.pull_request.head.repo.fork }}\"\n        echo \"Branch: ${{ github.event.pull_request.head.ref }}\"\n\n        if [ \"${{ github.event.pull_request.head.repo.fork }}\" == \"true\" ] && [ \"${{ github.event.pull_request.head.ref }}\" == 'main' ]; then\n          echo \"Error 🛑: PRs from the main branch of forked repositories are not allowed.\"\n          echo \"  Please create a named branch and resubmit the PR.\"\n          echo \"  See https://github.com/jaegertracing/jaeger/blob/main/CONTRIBUTING_GUIDELINES.md#branches\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/actions/setup-branch/action.yml",
    "content": "name: 'Setup BRANCH'\ndescription: 'Make BRANCH var accessible to job'\nruns:\n  using: \"composite\"\n  steps:\n    - name: Setup BRANCH\n      shell: bash\n      run: |\n        echo GITHUB_EVENT_NAME=${GITHUB_EVENT_NAME}\n        echo GITHUB_HEAD_REF=${GITHUB_HEAD_REF}\n        echo GITHUB_REF=${GITHUB_REF}\n        case ${GITHUB_EVENT_NAME} in\n          pull_request)\n            BRANCH=${GITHUB_HEAD_REF}\n            if [[ $BRANCH == 'main' ]]; then\n              BRANCH=main_from_fork\n            fi\n            ;;\n          *)\n            BRANCH=${GITHUB_REF##*/}\n            ;;\n        esac\n        echo \"we are on branch=${BRANCH}\"\n        echo \"BRANCH=${BRANCH}\" >> ${GITHUB_ENV}\n"
  },
  {
    "path": ".github/actions/setup-go-tip/action.yml",
    "content": "# Inspired by https://github.com/actions/setup-go/issues/21#issuecomment-997208686\nname: 'Install Go Tip'\ndescription: 'Install Go Tip toolchain'\ninputs:\n  gh_token:\n    description: 'The GitHub Token'\n    required: true\nruns:\n  using: \"composite\"\n  steps:\n    - name: Download pre-built Go tip from grafana/gotip repo\n      id: download\n      shell: bash\n      run: |\n        set -euo pipefail\n        gh release download ubuntu-latest --repo grafana/gotip --pattern 'go.zip'\n        echo \"::group::unzip\"\n        unzip go.zip -d $HOME/sdk\n        echo \"::endgroup::\"\n        export GOROOT=\"$HOME/sdk/gotip\"\n        echo \"GOROOT=$GOROOT\" >> $GITHUB_ENV\n        echo \"success=true\" >> $GITHUB_OUTPUT\n      env:\n        GH_TOKEN: ${{ inputs.gh_token }}\n\n    # If download failed, we will try to build tip from source.\n    # This requires Go toolchain, so install it first.\n    # However, gotip is picky about the version of Go used to build it,\n    # sometimes it requires the latest version, so determine it dynamically.\n    - name: Determine latest Go version\n      if: steps.download.outputs.success == 'false'\n      id: get_go_version\n      shell: bash\n      run: |\n        LATEST_GO_VERSION=$(curl -s \"https://go.dev/VERSION?m=text\" | grep go1 | sed 's/^go//g')\n        echo \"LATEST_GO_VERSION=$LATEST_GO_VERSION\" >> $GITHUB_OUTPUT\n\n    - name: Install Go toolchain\n      if: steps.download.outputs.success == 'false'\n      uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: ${{ steps.get_go_version.outputs.LATEST_GO_VERSION }}\n\n    - name: Build Go Tip from source\n      if: steps.download.outputs.success == 'false'\n      shell: bash\n      run: |\n        echo Build Go Tip from source\n        set -euo pipefail\n        go install golang.org/dl/gotip@latest\n        gotip download\n        export GOROOT=\"$(gotip env GOROOT)\"\n        echo \"GOROOT=$GOROOT\" >> $GITHUB_ENV\n        # for some reason even though we put gotip at the front of PATH later,\n        # the go binary installed in previous step still takes precedence. So remove it.\n        rm -f $(which go)\n\n    - name: Setup Go environment\n      shell: bash\n      run: |\n        echo Setup Go environment\n        set -euo pipefail\n        $GOROOT/bin/go version\n        GOPATH=\"$HOME/gotip\"\n        PATH=\"$GOROOT/bin:$GOPATH/bin:$PATH\"\n        echo \"GOPATH=$GOPATH\" >> $GITHUB_ENV\n        echo \"PATH=$PATH\" >> $GITHUB_ENV\n\n    - name: Check Go Version\n      shell: bash\n      run: |\n        echo Check Go Version\n        set -euo pipefail\n        echo \"GOPATH=$GOPATH\"\n        echo \"GOROOT=$GOROOT\"\n        echo \"which go:\"\n        which -a go\n        echo \"Active Go version:\"\n        go version\n"
  },
  {
    "path": ".github/actions/setup-node.js/action.yml",
    "content": "name: 'Setup Node.js'\ndescription: 'Setup Node.js version as required by jaeger-ui repo. Must be called after checkout with submodules.'\nruns:\n  using: \"composite\"\n  steps:\n    - name: Get Node.js version from jaeger-ui\n      shell: bash\n      run: |\n        echo \"JAEGER_UI_NODE_JS_VERSION=$(cat jaeger-ui/.nvmrc)\" >> ${GITHUB_ENV}\n\n    - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0\n      with:\n        node-version: ${{ env.JAEGER_UI_NODE_JS_VERSION }}\n        cache: 'npm'\n        cache-dependency-path: jaeger-ui/package-lock.json\n"
  },
  {
    "path": ".github/actions/upload-codecov/action.yml",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Codecov upload often fails on rate limits if used without a token.\n# See https://github.com/codecov/codecov-action/issues/837\n# This action embeds a token directly.\n# We cannot define it as \"secret\" as we need it accessible from forks.\n\nname: 'Upload coverage to codecov'\ndescription: 'Uploads coverage to codecov with retries and saves raw profiles as artifacts'\ninputs:\n  files:\n    description: 'Coverage files to upload (comma-separated)'\n    required: true\n  flag:\n    description: 'Codecov flag for this upload; also used as the artifact name suffix (coverage-<flag>)'\n    required: true\nruns:\n  using: 'composite'\n  steps:\n    # Upload raw profiles first so they are available to the fan-in workflow\n    # even if the Codecov upload below fails (e.g. rate-limit).\n    - name: Stage coverage files for artifact upload\n      shell: bash\n      run: |\n        set -x\n        mkdir -p /tmp/coverage-staging\n        IFS=',' read -ra FILES <<< \"${{ inputs.files }}\"\n        echo \"Staging ${#FILES[@]} coverage file(s): ${{ inputs.files }}\"\n        for f in \"${FILES[@]}\"; do\n          f=$(echo \"$f\" | xargs)  # trim whitespace\n          if [ -f \"$f\" ]; then\n            echo \"Staging: $f\"\n            cp \"$f\" /tmp/coverage-staging/\n          else\n            echo \"Warning: coverage file not found, skipping: $f\"\n          fi\n        done\n        echo \"Staged files:\"\n        ls -la /tmp/coverage-staging/\n\n    - name: Upload coverage profiles as artifact\n      uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n      with:\n        name: coverage-${{ inputs.flag }}\n        path: /tmp/coverage-staging/\n        retention-days: 7\n\n    - name: Retry upload\n      uses: Wandalen/wretry.action@e68c23e6309f2871ca8ae4763e7629b9c258e1ea # v3.8.0\n      with:\n        attempt_limit: 6\n        # sleep 10 seconds between retries\n        attempt_delay: 10000\n        action: codecov/codecov-action@7afa10ed9b269c561c2336fd862446844e0cbf71 # v4.2.0\n        with: |\n          files: ${{ inputs.files }}\n          flags: ${{ inputs.flag }}\n          verbose: true\n          fail_ci_if_error: false\n          token: f457b710-93af-4191-8678-bcf51281f98c\n"
  },
  {
    "path": ".github/actions/verify-metrics-snapshot/action.yaml",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nname: 'Verify Metric Snapshot and Upload Metrics'\ndescription: 'Upload or cache the metrics data after verification'\ninputs:\n  snapshot:\n    description: 'Path to the metric file'\n    required: true\n  artifact_key:\n    description: 'Artifact key used for uploading and fetching artifacts'\n    required: true\nruns:\n  using: 'composite'\n  steps:\n    - name: Upload current metrics snapshot\n      uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n      with:\n        name: ${{ inputs.artifact_key }}\n        path: ./.metrics/${{ inputs.snapshot }}.txt\n        retention-days: 7\n\n      # The github cache restore successfully restores when cache saved has same key and same path.\n      # Hence to restore release metric with name relese_{metric_name} , the name must be changed to the same.\n    - name: Change file name before caching\n      if: github.ref_name == 'main'\n      shell: bash\n      run: |\n        mv ./.metrics/${{ inputs.snapshot }}.txt ./.metrics/baseline_${{ inputs.snapshot }}.txt\n\n    - name: Cache metrics snapshot on main branch for longer retention\n      if: github.ref_name == 'main'\n      uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57\n      with:\n        path: ./.metrics/baseline_${{ inputs.snapshot }}.txt\n        key: ${{ inputs.artifact_key }}_${{ github.run_id }}\n\n    # Use restore keys to match prefix and fetch the latest cache\n    # Here , restore keys is an ordered list of prefixes that need to be matched\n    - name: Download the cached tagged metrics\n      id: download-release-snapshot\n      if: github.ref_name != 'main'\n      uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57\n      with:\n        path: ./.metrics/baseline_${{ inputs.snapshot }}.txt\n        key: ${{ inputs.artifact_key }}\n        restore-keys: |\n          ${{ inputs.artifact_key }}\n\n    # Create an empty stub so the diff artifact is always uploaded on PRs.\n    # The fan-in uses 1-to-1 presence of diff artifacts to detect infra failures\n    # (a missing diff_* artifact means this action never ran for that snapshot).\n    # The stub is overwritten by the compare step when a baseline exists.\n    - name: Create diff file stub\n      if: github.ref_name != 'main'\n      shell: bash\n      run: touch ./.metrics/diff_${{ inputs.snapshot }}.txt\n\n    - name: Calculate diff between the snapshots\n      id: compare-snapshots\n      if: ${{ (github.ref_name != 'main') && (steps.download-release-snapshot.outputs.cache-matched-key != '')  }}\n      continue-on-error: true\n      shell: bash\n      run: |\n        python3 -m pip install prometheus-client\n        if python3 ./scripts/e2e/compare_metrics.py --file1 ./.metrics/${{ inputs.snapshot }}.txt --file2 ./.metrics/baseline_${{ inputs.snapshot }}.txt --output ./.metrics/diff_${{ inputs.snapshot }}.txt; then\n          echo \"No differences found in metrics\"\n        else\n          echo \"🛑 Differences found in metrics\"\n          echo \"has_diff=true\" >> $GITHUB_OUTPUT\n        fi\n\n    # Always upload the diff artifact on PRs (even when empty / no baseline yet).\n    # Presence of this artifact in the fan-in proves this action ran for the snapshot.\n    - name: Upload the diff artifact\n      if: github.ref_name != 'main'\n      uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n      with:\n        name: diff_${{ inputs.artifact_key }}\n        path: ./.metrics/diff_${{ inputs.snapshot }}.txt\n        retention-days: 7\n"
  },
  {
    "path": ".github/scripts/.gitignore",
    "content": "node_modules/\n"
  },
  {
    "path": ".github/scripts/README.md",
    "content": "# PR Quota Manager - Manual Execution Guide\n\nThis document explains how to run the PR Quota Manager script manually from the command line for testing and troubleshooting.\n\n## Prerequisites\n\n1. **Node.js** (version 16 or higher)\n   ```bash\n   node --version\n   ```\n\n2. **GitHub Personal Access Token** with the following permissions:\n   - `repo` (Full control of private repositories)\n   - `public_repo` (Access public repositories) - if working with public repos only\n\n   Create a token at: https://github.com/settings/tokens.\n   Store the value in a file, e.g. `~/.github_token`.\n   Then set the environment variable:\n   ```bash\n      read -r GITHUB_TOKEN < ~/.github_token\n      export GITHUB_TOKEN\n   ```\n\n3. **Install Dependencies**\n   \n   Navigate to the `.github/scripts` directory and install dependencies:\n   ```bash\n   cd .github/scripts\n   npm ci\n   ```\n\n## Running the Script\n\n### Basic Usage\n\n```bash\nnode pr-quota-manager.js <username> [owner] [repo]\n```\n\n### Parameters\n\n- `username` (required): The GitHub username to process quota for\n- `owner` (optional): Repository owner (defaults to `jaegertracing` or `GITHUB_REPOSITORY` env var)\n- `repo` (optional): Repository name (defaults to `jaeger` or `GITHUB_REPOSITORY` env var)\n\n### Examples\n\n**Process quota for a specific user in the default repository:**\n```bash\nnode pr-quota-manager.js newcontributor\n```\n\n**Process quota for a user in a different repository:**\n```bash\nnode pr-quota-manager.js username myorg myrepo\n```\n\n**Using environment variables for repository:**\n```bash\nexport GITHUB_REPOSITORY=\"jaegertracing/jaeger\"\nnode pr-quota-manager.js contributor\n```\n\n### Dry-Run Mode\n\nTest the script without making any actual changes:\n\n```bash\n# Using flag\nnode pr-quota-manager.js username --dry-run\n\n# Using environment variable\nDRY_RUN=true node pr-quota-manager.js username\n```\n\nIn dry-run mode, the script will:\n- Show exactly what actions it would take\n- Not create/modify labels\n- Not post comments\n- Not make any API modifications\n- Still fetch and analyze PRs for accurate simulation\n\n## Listing Open PRs by Author\n\nUse the utility script to see all open PRs grouped by author:\n\n```bash\nnode list-open-prs-by-author.js [owner] [repo]\n```\n\nThis is useful for:\n- Identifying which users need quota processing\n- Planning backfills of the quota system\n- Seeing which PRs are already quota-blocked\n\n**CSV output for scripting:**\n```bash\nFORMAT=csv node list-open-prs-by-author.js > prs.csv\n```\n\n## Output\n\nThe script will display:\n\n1. **History Audit**: Summary of merged PR count (up to 3 merged PRs for quota calculation)\n2. **Current Stats**: Merged count, calculated quota, and open PR count\n3. **Processing Actions**: Each PR being blocked/unblocked\n4. **Summary**: Total counts of blocked, unblocked, and unchanged PRs\n\n### Example Output\n\n```\n=== Processing Quota for: @newuser ===\n\n📜 History Audit:\n  No merged PRs found.\n\n📊 Current Stats:\n  User has 0 merged PRs. Current Quota: 1. Currently Open: 3.\n\n🔄 Processing Open PRs:\n\n  ℹ️  PR #123 unchanged (active)\n  ✅ Labeled PR #124 as blocked (Position: 2/3, Quota: 1)\n  ✅ Labeled PR #125 as blocked (Position: 3/3, Quota: 1)\n\n✅ Processing Complete for @newuser\n\n📋 Summary:\n  - Blocked: 2 PRs\n  - Unblocked: 0 PRs\n  - Unchanged: 1 PRs\n```\n\n## Running Tests\n\nTo run the unit tests:\n\n```bash\ncd .github/scripts\nnpm test\n```\n\nTo run tests with coverage:\n\n```bash\nnpm test -- --coverage\n```\n\n## Quota Rules\n\nThe script applies the following quota rules:\n\n| Merged PRs | Quota |\n|-----------|-------|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |\n| 3+ | 10 |\n\n## Troubleshooting\n\n### \"GITHUB_TOKEN environment variable is required\"\n\nMake sure you've set the `GITHUB_TOKEN` environment variable:\n```bash\nexport GITHUB_TOKEN=\"your_token_here\"\n```\n\n### \"403 Forbidden\" errors\n\nYour GitHub token may not have the required permissions. Ensure it has:\n- `repo` scope for private repositories\n- `public_repo` scope for public repositories\n\n### \"Cannot find module '@octokit/rest'\"\n\nInstall the required dependency:\n```bash\ncd .github/scripts\nnpm install @octokit/rest\n```\n\n### API Rate Limiting\n\nGitHub has rate limits for API requests:\n- Authenticated requests: 5,000 requests per hour\n- The script makes approximately 2-5 API calls per user\n\nIf you hit rate limits, wait for the limit to reset or use a different token.\n\n## How It Works\n\n1. **Fetches PRs** by the target author (all open PRs + up to 3 merged PRs for quota calculation)\n2. **Calculates quota** based on the number of merged PRs\n3. **Identifies open PRs** and sorts them by creation date (oldest first)\n4. **Applies labels** to PRs based on quota:\n   - PRs within quota: Remove `pr-quota-reached` label (if present)\n   - PRs exceeding quota: Add `pr-quota-reached` label\n5. **Posts comments** (only on state changes to avoid spam):\n   - Blocking comment when PR first gets blocked\n   - Unblocking comment when PR is moved to active queue\n\n## Integration with GitHub Actions\n\nThis script is automatically executed by the GitHub Actions workflow (`.github/workflows/pr-quota-manager.yml`) on:\n- Pull request opened, closed, or reopened events\n- Manual workflow dispatch\n\nThe workflow uses `actions/github-script` to run the script with the repository's built-in `GITHUB_TOKEN`.\n"
  },
  {
    "path": ".github/scripts/ci-summary-report-publish.js",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n// SECURITY WARNING — INJECTION RISK\n//\n// This script runs in the BASE REPOSITORY context (via workflow_run) with\n// pull-requests: write and checks: write permissions. The ci-summary artifact\n// it reads was produced by a PR's CI run and may originate from a FORK,\n// containing UNTRUSTED content crafted by the PR author.\n//\n// NEVER interpolate artifact content verbatim into PR comments, check run\n// summaries, or any GitHub API call. Doing so allows a malicious PR to inject\n// arbitrary Markdown or URLs into the repository's UI.\n//\n// Required invariants maintained by this file:\n//   1. ci-summary.json contains ONLY typed primitives: numbers, booleans, and\n//      fixed enum strings (\"success\"/\"failure\"/\"skipped\"). No free-form text.\n//   2. All display text (PR comments, check summaries) is constructed entirely\n//      from trusted template strings defined in this file.\n//   3. Numeric values are coerced through safeNum() which validates with\n//      Number.isFinite() and rejects negatives.\n//   4. Boolean fields are compared with === true, never coerced with !! which\n//      would misinterpret a JSON string \"false\" as truthy.\n//   5. String fields from the artifact are used only in comparisons\n//      (=== 'success'), never interpolated into output strings.\n\n'use strict';\n\n// HTML comment tag used to identify the CI summary comment on a PR.\nconst COMMENT_TAG = '<!-- ci-summary-report -->';\n\n/**\n * Coerce a value from ci-summary.json to a non-negative number, or null.\n * Returns null for null/undefined inputs (preserves \"step did not run\" signal).\n * Returns null for NaN, Infinity, or negative values.\n * @param {*} v\n * @returns {number|null}\n */\nfunction safeNum(v) {\n  if (v === null || v === undefined) return null;\n  const n = Number(v);\n  return Number.isFinite(n) && n >= 0 ? n : null;\n}\n\n// Prometheus metric names: must start with [a-zA-Z_:], followed by [a-zA-Z0-9_:].\nconst METRIC_NAME_RE = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;\nconst MAX_METRIC_NAME_LEN = 200;\nconst MAX_SNAPSHOT_NAME_LEN = 200;\nconst MAX_SNAPSHOTS = 50;\nconst MAX_METRIC_NAMES_PER_SNAPSHOT = 200;\n// Snapshot names come from artifact directory names (alphanumeric, underscores,\n// dots, hyphens).  We reject anything outside this character set.\nconst SNAPSHOT_NAME_RE = /^[a-zA-Z0-9_.\\-]+$/;\n\n/**\n * Validate and sanitize a Prometheus metric name.\n * Returns the name if valid, null otherwise.\n * @param {*} name\n * @returns {string|null}\n */\nfunction sanitizeMetricName(name) {\n  if (typeof name !== 'string') return null;\n  if (name.length === 0 || name.length > MAX_METRIC_NAME_LEN) return null;\n  return METRIC_NAME_RE.test(name) ? name : null;\n}\n\n/**\n * Validate and sanitize the metrics_snapshots array from ci-summary.json.\n * Each entry is validated: counts go through safeNum(), metric names through\n * sanitizeMetricName(). Invalid entries or fields are silently dropped.\n * @param {*} raw - The raw metrics_snapshots value from the artifact\n * @returns {Array|null} - Sanitized array, or null if input is missing/invalid\n */\nfunction sanitizeSnapshots(raw) {\n  if (!Array.isArray(raw)) return null;\n  const result = [];\n  for (const entry of raw) {\n    if (result.length >= MAX_SNAPSHOTS) break;\n    if (typeof entry !== 'object' || entry === null) continue;\n    // Validate snapshot name\n    const snapshot = typeof entry.snapshot === 'string'\n      && entry.snapshot.length > 0\n      && entry.snapshot.length <= MAX_SNAPSHOT_NAME_LEN\n      && SNAPSHOT_NAME_RE.test(entry.snapshot)\n      ? entry.snapshot : null;\n    if (!snapshot) continue;\n    // Validate counts\n    const added    = safeNum(entry.added);\n    const removed  = safeNum(entry.removed);\n    const modified = safeNum(entry.modified);\n    // Validate metric_names array — collect up to cap valid names\n    const names = [];\n    if (Array.isArray(entry.metric_names)) {\n      for (const n of entry.metric_names) {\n        if (names.length >= MAX_METRIC_NAMES_PER_SNAPSHOT) break;\n        const clean = sanitizeMetricName(n);\n        if (clean) names.push(clean);\n      }\n    }\n    result.push({ snapshot, added, removed, modified, metric_names: names });\n  }\n  return result.length > 0 ? result : null;\n}\n\n/**\n * Derive metrics conclusion and display text from the parsed ci-summary artifact.\n * Uses === true for boolean fields to avoid misinterpreting JSON strings.\n * @param {object} s - Parsed ci-summary.json\n * @returns {{ hasInfraErrors: boolean, totalChanges: number|null, snapshots: Array|null, conclusion: string, text: string }}\n */\nfunction computeMetrics(s) {\n  const hasInfraErrors = s.metrics_has_infra_errors === true;\n  const totalChanges   = safeNum(s.metrics_total_changes);\n  const snapshots      = sanitizeSnapshots(s.metrics_snapshots);\n  // Derive conclusion from the same conditions that drive text so they are always consistent.\n  const conclusion     = (hasInfraErrors || totalChanges === null || totalChanges > 0) ? 'failure' : 'success';\n\n  let text;\n  if (hasInfraErrors) {\n    text = '❌ Infrastructure error: missing diff artifacts';\n  } else if (totalChanges === null) {\n    text = '❌ Could not read metrics_total_changes from summary';\n  } else if (totalChanges > 0) {\n    text = `❌ ${totalChanges} metric change(s) detected`;\n  } else {\n    text = '✅ No significant metric changes';\n  }\n\n  return { hasInfraErrors, totalChanges, snapshots, conclusion, text };\n}\n\n/**\n * Derive coverage conclusion and display text from the parsed ci-summary artifact.\n * @param {object} s - Parsed ci-summary.json\n * @returns {{ skipped: boolean, conclusion: string, text: string }}\n */\nfunction computeCoverage(s) {\n  const skipped    = s.coverage_skipped === true || s.coverage_conclusion === 'skipped';\n  const conclusion = (skipped || s.coverage_conclusion === 'success') ? 'success' : 'failure';\n  const pct        = safeNum(s.coverage_percentage);\n  const baseline   = safeNum(s.coverage_baseline);\n\n  let text;\n  if (skipped) {\n    text = '⏭️ No coverage profiles found; coverage gate skipped.';\n  } else {\n    const pctStr      = pct      !== null ? `${pct}%`      : 'unknown';\n    const baselineStr = baseline !== null ? ` (baseline ${baseline}%)` : ' (no baseline)';\n    const icon        = conclusion === 'success' ? '✅' : '❌';\n    text              = `${icon} Coverage ${pctStr}${baselineStr}`;\n  }\n\n  return { skipped, conclusion, text };\n}\n\n/**\n * Format a detail breakdown of per-snapshot metric changes.\n * All text is built from trusted templates; metric names have been validated\n * through sanitizeMetricName() and are rendered in backtick-code spans.\n * @param {Array|null} snapshots - Sanitized snapshots from computeMetrics\n * @returns {string} - Markdown detail block, or empty string if no data\n */\nfunction formatMetricsDetail(snapshots) {\n  if (!snapshots || snapshots.length === 0) return '';\n\n  const lines = [\n    '',\n    '<details>',\n    '<summary>View changed metrics</summary>',\n    '',\n  ];\n\n  for (const snap of snapshots) {\n    lines.push(`**${snap.snapshot}**`);\n    const parts = [];\n    if (snap.added    !== null && snap.added    > 0) parts.push(`${snap.added} added`);\n    if (snap.removed  !== null && snap.removed  > 0) parts.push(`${snap.removed} removed`);\n    if (snap.modified !== null && snap.modified > 0) parts.push(`${snap.modified} modified`);\n    if (parts.length > 0) {\n      lines.push(parts.join(', '));\n    }\n    if (snap.metric_names.length > 0) {\n      for (const name of snap.metric_names) {\n        lines.push(`- \\`${name}\\``);\n      }\n    }\n    lines.push('');\n  }\n\n  lines.push('</details>');\n  return lines.join('\\n');\n}\n\n/**\n * Build the PR comment body from pre-computed display strings.\n * Inputs are strings produced by computeMetrics/computeCoverage: all display text\n * is constructed from trusted templates; artifact-derived values appear only as\n * validated primitives (numbers) embedded by those functions, never as raw strings.\n * @param {string} metricsText\n * @param {string} coverageText\n * @param {string} footer - links + timestamp line\n * @param {object} [opts]\n * @param {Array|null} [opts.metricsSnapshots] - sanitized snapshot data for detail rendering\n * @returns {string}\n */\nfunction buildCommentBody(metricsText, coverageText, footer, { metricsSnapshots } = {}) {\n  const parts = [\n    COMMENT_TAG,\n    '## CI Summary Report',\n    '',\n    '### Metrics Comparison',\n    metricsText,\n  ];\n\n  const detail = formatMetricsDetail(metricsSnapshots);\n  if (detail) {\n    parts.push(detail);\n  }\n\n  parts.push('');\n  parts.push('### Code Coverage');\n  parts.push(coverageText);\n  parts.push('');\n  parts.push(footer);\n\n  return parts.join('\\n');\n}\n\n/**\n * Create a completed check run and log the result.\n * @param {object} github - Octokit client\n * @param {string} owner\n * @param {string} repo\n * @param {string} headSha\n * @param {string} name\n * @param {string} conclusion\n * @param {object} output - { title, summary, text }\n * @param {object} core - GitHub Actions core logger\n */\nasync function postCheckRun(github, owner, repo, headSha, name, conclusion, output, core) {\n  core.info(`Creating check run: \"${name}\" (conclusion: ${conclusion})`);\n  const { data } = await github.rest.checks.create({\n    owner, repo,\n    head_sha: headSha,\n    name,\n    status: 'completed',\n    conclusion,\n    output,\n  });\n  core.info(`Check run created: id=${data.id} url=${data.html_url}`);\n}\n\n/**\n * Post or update the CI summary comment on a PR.\n * Always updates an existing comment (clears stale failure messages on green runs).\n * Only creates a new comment when createNew is true.\n * @param {object} github - Octokit client\n * @param {string} owner\n * @param {string} repo\n * @param {number} prNumber\n * @param {string} body\n * @param {object} core - GitHub Actions core logger\n * @param {object} [opts]\n * @param {boolean} [opts.createNew=true] - create a comment if none exists\n */\nasync function postOrUpdateComment(github, owner, repo, prNumber, body, core, { createNew = true } = {}) {\n  core.info(`Searching for existing CI summary comment on PR #${prNumber}`);\n  const existing = await github.paginate(github.rest.issues.listComments, {\n    owner, repo, issue_number: prNumber,\n  }).then(cs => cs.find(c => c.body && c.body.startsWith(COMMENT_TAG)));\n\n  if (existing) {\n    core.info(`Updating existing comment id=${existing.id}`);\n    const { data: updated } = await github.rest.issues.updateComment({\n      owner, repo, comment_id: existing.id, body,\n    });\n    core.info(`Comment updated: url=${updated.html_url}`);\n  } else if (createNew) {\n    core.info(`Creating new comment on PR #${prNumber}`);\n    const { data: created } = await github.rest.issues.createComment({\n      owner, repo, issue_number: prNumber, body,\n    });\n    core.info(`Comment created: id=${created.id} url=${created.html_url}`);\n  } else {\n    core.info('No existing comment and no issues to report; skipping PR comment.');\n  }\n}\n\n/**\n * GitHub Actions entry point.\n * Reads ci-summary.json, computes conclusions, posts check runs and PR comment.\n *\n * @param {object} opts\n * @param {object} opts.github  - Octokit client from actions/github-script\n * @param {object} opts.core    - GitHub Actions core logger\n * @param {object} opts.fs      - Node fs module (injected for testability)\n * @param {object} opts.inputs\n * @param {string} opts.inputs.owner\n * @param {string} opts.inputs.repo\n * @param {string} opts.inputs.headSha\n * @param {string} opts.inputs.prNumber  - raw string from step output\n * @param {string} opts.inputs.ciRunUrl\n * @param {string} opts.inputs.publishUrl\n */\nasync function handler({ github, core, fs, inputs }) {\n  const { owner, repo, headSha, ciRunUrl, publishUrl } = inputs;\n  const prNumber = parseInt(inputs.prNumber, 10) || null;\n\n  const links  = `➡️ [View CI run](${ciRunUrl}) | [View publish logs](${publishUrl})`;\n  const ts     = new Date().toISOString().replace('T', ' ').replace(/\\.\\d+Z$/, ' UTC');\n  const footer = `${links}\\n_${ts}_`;\n\n  // Read structured data written by ci-summary-report.yml.\n  // All fields are primitives (enums, numbers, booleans) — no free-form text.\n  let s;\n  try {\n    s = JSON.parse(fs.readFileSync('.artifacts/ci-summary.json', 'utf8'));\n  } catch (e) {\n    core.warning(`ci-summary.json not found or unparseable: ${e.message}`);\n    // Post failing check runs so required status checks are never silently absent.\n    // All text here is a trusted, fixed string — no artifact content is used.\n    const errorSummary = 'ci-summary artifact missing or unparseable; check CI run logs.';\n    for (const name of ['Metrics Comparison', 'Coverage Gate']) {\n      await postCheckRun(github, owner, repo, headSha, name, 'failure',\n        { title: name, summary: errorSummary, text: footer }, core);\n    }\n    return;\n  }\n\n  const metrics  = computeMetrics(s);\n  const coverage = computeCoverage(s);\n\n  await postCheckRun(github, owner, repo, headSha, 'Metrics Comparison', metrics.conclusion, {\n    title:   'Metrics Comparison Result',\n    summary: metrics.text,\n    text:    `Total changes across all snapshots: ${metrics.totalChanges ?? 'unknown'}\\n\\n${footer}`,\n  }, core);\n\n  // Always created so it can be used as a required status check.\n  await postCheckRun(github, owner, repo, headSha, 'Coverage Gate', coverage.conclusion, {\n    title:   'Coverage Gate',\n    summary: coverage.text,\n    text:    footer,\n  }, core);\n\n  // ── PR comment ──\n  if (prNumber) {\n    // Always update an existing comment so stale failure messages don't linger\n    // after a green run.  Only create a new comment when there is something to report.\n    const hasIssues = metrics.conclusion === 'failure' || coverage.conclusion === 'failure'\n                      || metrics.totalChanges > 0;\n    const body = buildCommentBody(metrics.text, coverage.text, footer, { metricsSnapshots: metrics.snapshots });\n    await postOrUpdateComment(github, owner, repo, prNumber, body, core, { createNew: hasIssues });\n  } else {\n    core.info('No PR number; skipping PR comment.');\n  }\n}\n\nmodule.exports = handler;\nmodule.exports.safeNum              = safeNum;\nmodule.exports.sanitizeMetricName   = sanitizeMetricName;\nmodule.exports.sanitizeSnapshots    = sanitizeSnapshots;\nmodule.exports.computeMetrics       = computeMetrics;\nmodule.exports.computeCoverage      = computeCoverage;\nmodule.exports.formatMetricsDetail  = formatMetricsDetail;\nmodule.exports.buildCommentBody     = buildCommentBody;\nmodule.exports.postCheckRun         = postCheckRun;\nmodule.exports.postOrUpdateComment  = postOrUpdateComment;\nmodule.exports.COMMENT_TAG          = COMMENT_TAG;\n"
  },
  {
    "path": ".github/scripts/ci-summary-report-publish.test.js",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n'use strict';\n\nconst {\n  safeNum,\n  sanitizeMetricName,\n  sanitizeSnapshots,\n  computeMetrics,\n  computeCoverage,\n  formatMetricsDetail,\n  buildCommentBody,\n  postCheckRun,\n  postOrUpdateComment,\n  COMMENT_TAG,\n} = require('./ci-summary-report-publish');\n\n// ── safeNum ──────────────────────────────────────────────────────────────────\n\ndescribe('safeNum', () => {\n  test('returns null for null', () => expect(safeNum(null)).toBeNull());\n  test('returns null for undefined', () => expect(safeNum(undefined)).toBeNull());\n  test('returns 0 for 0', () => expect(safeNum(0)).toBe(0));\n  test('returns integer value', () => expect(safeNum(5)).toBe(5));\n  test('returns float value', () => expect(safeNum(96.8)).toBe(96.8));\n  test('returns null for negative number', () => expect(safeNum(-1)).toBeNull());\n  test('returns null for NaN', () => expect(safeNum(NaN)).toBeNull());\n  test('returns null for Infinity', () => expect(safeNum(Infinity)).toBeNull());\n  test('coerces numeric string', () => expect(safeNum('42')).toBe(42));\n  test('returns null for non-numeric string', () => expect(safeNum('bad')).toBeNull());\n});\n\n// ── sanitizeMetricName ───────────────────────────────────────────────────────\n\ndescribe('sanitizeMetricName', () => {\n  test('accepts valid Prometheus metric name', () => {\n    expect(sanitizeMetricName('http_server_duration')).toBe('http_server_duration');\n  });\n  test('accepts name with colons', () => {\n    expect(sanitizeMetricName('rpc:server_duration:total')).toBe('rpc:server_duration:total');\n  });\n  test('accepts name starting with underscore', () => {\n    expect(sanitizeMetricName('_internal_metric')).toBe('_internal_metric');\n  });\n  test('rejects empty string', () => {\n    expect(sanitizeMetricName('')).toBeNull();\n  });\n  test('rejects name starting with digit', () => {\n    expect(sanitizeMetricName('0invalid')).toBeNull();\n  });\n  test('rejects name with spaces', () => {\n    expect(sanitizeMetricName('metric name')).toBeNull();\n  });\n  test('rejects markdown injection', () => {\n    expect(sanitizeMetricName('[click me](http://evil.com)')).toBeNull();\n  });\n  test('rejects HTML injection', () => {\n    expect(sanitizeMetricName('<img src=x onerror=alert(1)>')).toBeNull();\n  });\n  test('rejects name with curly braces', () => {\n    expect(sanitizeMetricName('metric{label=\"value\"}')).toBeNull();\n  });\n  test('rejects non-string types', () => {\n    expect(sanitizeMetricName(42)).toBeNull();\n    expect(sanitizeMetricName(null)).toBeNull();\n    expect(sanitizeMetricName(undefined)).toBeNull();\n    expect(sanitizeMetricName({})).toBeNull();\n  });\n  test('rejects names exceeding 200 characters', () => {\n    expect(sanitizeMetricName('a'.repeat(201))).toBeNull();\n  });\n  test('accepts names at exactly 200 characters', () => {\n    const name = 'a'.repeat(200);\n    expect(sanitizeMetricName(name)).toBe(name);\n  });\n});\n\n// ── sanitizeSnapshots ────────────────────────────────────────────────────────\n\ndescribe('sanitizeSnapshots', () => {\n  test('returns null for non-array input', () => {\n    expect(sanitizeSnapshots(null)).toBeNull();\n    expect(sanitizeSnapshots(undefined)).toBeNull();\n    expect(sanitizeSnapshots('string')).toBeNull();\n    expect(sanitizeSnapshots({})).toBeNull();\n  });\n\n  test('returns null for empty array', () => {\n    expect(sanitizeSnapshots([])).toBeNull();\n  });\n\n  test('sanitizes valid snapshot entry', () => {\n    const input = [{\n      snapshot: 'metrics_snapshot_cassandra',\n      added: 2, removed: 1, modified: 0,\n      metric_names: ['http_server_duration', 'rpc_client_duration'],\n    }];\n    const result = sanitizeSnapshots(input);\n    expect(result).toHaveLength(1);\n    expect(result[0].snapshot).toBe('metrics_snapshot_cassandra');\n    expect(result[0].added).toBe(2);\n    expect(result[0].removed).toBe(1);\n    expect(result[0].modified).toBe(0);\n    expect(result[0].metric_names).toEqual(['http_server_duration', 'rpc_client_duration']);\n  });\n\n  test('drops entries with invalid snapshot names', () => {\n    const input = [\n      { snapshot: 'valid_name', added: 1, removed: 0, modified: 0, metric_names: [] },\n      { snapshot: '<script>alert(1)</script>', added: 1, removed: 0, modified: 0, metric_names: [] },\n      { snapshot: 'also.valid-name.2', added: 0, removed: 1, modified: 0, metric_names: [] },\n    ];\n    const result = sanitizeSnapshots(input);\n    expect(result).toHaveLength(2);\n    expect(result[0].snapshot).toBe('valid_name');\n    expect(result[1].snapshot).toBe('also.valid-name.2');\n  });\n\n  test('drops invalid metric names from metric_names array', () => {\n    const input = [{\n      snapshot: 'test_snapshot',\n      added: 2, removed: 0, modified: 0,\n      metric_names: ['valid_metric', '<injected>', 'another_valid'],\n    }];\n    const result = sanitizeSnapshots(input);\n    expect(result[0].metric_names).toEqual(['valid_metric', 'another_valid']);\n  });\n\n  test('caps snapshots at 50 entries', () => {\n    const input = Array.from({ length: 60 }, (_, i) => ({\n      snapshot: `snap_${i}`,\n      added: 1, removed: 0, modified: 0,\n      metric_names: [],\n    }));\n    const result = sanitizeSnapshots(input);\n    expect(result).toHaveLength(50);\n  });\n\n  test('caps metric_names at 200 per snapshot', () => {\n    const names = Array.from({ length: 210 }, (_, i) => `metric_${i}`);\n    const input = [{\n      snapshot: 'test_snapshot',\n      added: 210, removed: 0, modified: 0,\n      metric_names: names,\n    }];\n    const result = sanitizeSnapshots(input);\n    expect(result[0].metric_names).toHaveLength(200);\n  });\n\n  test('handles missing metric_names gracefully', () => {\n    const input = [{\n      snapshot: 'test_snapshot',\n      added: 1, removed: 0, modified: 0,\n    }];\n    const result = sanitizeSnapshots(input);\n    expect(result[0].metric_names).toEqual([]);\n  });\n\n  test('validates counts through safeNum', () => {\n    const input = [{\n      snapshot: 'test_snapshot',\n      added: -1, removed: 'bad', modified: Infinity,\n      metric_names: ['valid_metric'],\n    }];\n    const result = sanitizeSnapshots(input);\n    expect(result[0].added).toBeNull();\n    expect(result[0].removed).toBeNull();\n    expect(result[0].modified).toBeNull();\n  });\n\n  test('skips null and non-object entries', () => {\n    const input = [null, 42, 'string', { snapshot: 'valid', added: 0, removed: 0, modified: 0, metric_names: [] }];\n    const result = sanitizeSnapshots(input);\n    expect(result).toHaveLength(1);\n    expect(result[0].snapshot).toBe('valid');\n  });\n});\n\n// ── computeMetrics ────────────────────────────────────────────────────────────\n\ndescribe('computeMetrics', () => {\n  test('success when no changes and no infra errors', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: false, metrics_total_changes: 0 });\n    expect(r.conclusion).toBe('success');\n    expect(r.text).toBe('✅ No significant metric changes');\n    expect(r.totalChanges).toBe(0);\n    expect(r.hasInfraErrors).toBe(false);\n    expect(r.snapshots).toBeNull();\n  });\n\n  test('failure when total changes > 0', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: false, metrics_total_changes: 3 });\n    expect(r.conclusion).toBe('failure');\n    expect(r.text).toBe('❌ 3 metric change(s) detected');\n    expect(r.totalChanges).toBe(3);\n  });\n\n  test('failure when infra errors present', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: true, metrics_total_changes: 0 });\n    expect(r.conclusion).toBe('failure');\n    expect(r.text).toBe('❌ Infrastructure error: missing diff artifacts');\n    expect(r.hasInfraErrors).toBe(true);\n  });\n\n  test('failure when total_changes is null (step did not write output)', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: false, metrics_total_changes: null });\n    expect(r.conclusion).toBe('failure');\n    expect(r.text).toBe('❌ Could not read metrics_total_changes from summary');\n    expect(r.totalChanges).toBeNull();\n  });\n\n  test('infra errors take precedence over missing total_changes', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: true, metrics_total_changes: null });\n    expect(r.conclusion).toBe('failure');\n    expect(r.text).toBe('❌ Infrastructure error: missing diff artifacts');\n  });\n\n  // Ensure JSON string \"false\" / \"true\" are not coerced as booleans\n  test('treats string \"true\" for metrics_has_infra_errors as falsy (not === true)', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: 'true', metrics_total_changes: 0 });\n    expect(r.hasInfraErrors).toBe(false);\n  });\n\n  test('treats string \"false\" for metrics_has_infra_errors as falsy', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: 'false', metrics_total_changes: 0 });\n    expect(r.hasInfraErrors).toBe(false);\n  });\n\n  test('includes sanitized snapshots when present', () => {\n    const r = computeMetrics({\n      metrics_has_infra_errors: false,\n      metrics_total_changes: 2,\n      metrics_snapshots: [{\n        snapshot: 'cassandra_v2',\n        added: 1, removed: 1, modified: 0,\n        metric_names: ['http_server_duration', 'rpc_client_duration'],\n      }],\n    });\n    expect(r.snapshots).toHaveLength(1);\n    expect(r.snapshots[0].snapshot).toBe('cassandra_v2');\n    expect(r.snapshots[0].metric_names).toEqual(['http_server_duration', 'rpc_client_duration']);\n  });\n\n  test('returns null snapshots when metrics_snapshots is absent', () => {\n    const r = computeMetrics({ metrics_has_infra_errors: false, metrics_total_changes: 0 });\n    expect(r.snapshots).toBeNull();\n  });\n});\n\n// ── formatMetricsDetail ──────────────────────────────────────────────────────\n\ndescribe('formatMetricsDetail', () => {\n  test('returns empty string for null snapshots', () => {\n    expect(formatMetricsDetail(null)).toBe('');\n  });\n\n  test('returns empty string for empty array', () => {\n    expect(formatMetricsDetail([])).toBe('');\n  });\n\n  test('renders single snapshot with metric names', () => {\n    const detail = formatMetricsDetail([{\n      snapshot: 'cassandra_v2',\n      added: 1, removed: 0, modified: 1,\n      metric_names: ['http_server_duration', 'rpc_client_duration'],\n    }]);\n    expect(detail).toContain('<details>');\n    expect(detail).toContain('</details>');\n    expect(detail).toContain('View changed metrics');\n    expect(detail).toContain('**cassandra_v2**');\n    expect(detail).toContain('1 added, 1 modified');\n    expect(detail).toContain('- `http_server_duration`');\n    expect(detail).toContain('- `rpc_client_duration`');\n  });\n\n  test('renders multiple snapshots', () => {\n    const detail = formatMetricsDetail([\n      { snapshot: 'snap_a', added: 2, removed: 0, modified: 0, metric_names: ['metric_a'] },\n      { snapshot: 'snap_b', added: 0, removed: 3, modified: 0, metric_names: ['metric_b'] },\n    ]);\n    expect(detail).toContain('**snap_a**');\n    expect(detail).toContain('**snap_b**');\n    expect(detail).toContain('2 added');\n    expect(detail).toContain('3 removed');\n  });\n\n  test('omits zero counts from summary line', () => {\n    const detail = formatMetricsDetail([{\n      snapshot: 'test', added: 0, removed: 5, modified: 0,\n      metric_names: [],\n    }]);\n    expect(detail).not.toContain('added');\n    expect(detail).toContain('5 removed');\n    expect(detail).not.toContain('modified');\n  });\n});\n\n// ── computeCoverage ───────────────────────────────────────────────────────────\n\ndescribe('computeCoverage', () => {\n  test('success with pct and baseline', () => {\n    const r = computeCoverage({\n      coverage_skipped: false,\n      coverage_conclusion: 'success',\n      coverage_percentage: 96.8,\n      coverage_baseline: 46.4,\n    });\n    expect(r.conclusion).toBe('success');\n    expect(r.skipped).toBe(false);\n    expect(r.text).toBe('✅ Coverage 96.8% (baseline 46.4%)');\n  });\n\n  test('failure when conclusion is failure', () => {\n    const r = computeCoverage({\n      coverage_skipped: false,\n      coverage_conclusion: 'failure',\n      coverage_percentage: 94.0,\n      coverage_baseline: 96.0,\n    });\n    expect(r.conclusion).toBe('failure');\n    expect(r.text).toBe('❌ Coverage 94% (baseline 96%)');\n  });\n\n  test('skipped when coverage_skipped is true', () => {\n    const r = computeCoverage({ coverage_skipped: true, coverage_conclusion: 'success' });\n    expect(r.skipped).toBe(true);\n    expect(r.conclusion).toBe('success');\n    expect(r.text).toBe('⏭️ No coverage profiles found; coverage gate skipped.');\n  });\n\n  test('skipped when coverage_conclusion is \"skipped\"', () => {\n    const r = computeCoverage({ coverage_skipped: false, coverage_conclusion: 'skipped' });\n    expect(r.skipped).toBe(true);\n    expect(r.conclusion).toBe('success');\n  });\n\n  test('shows \"unknown\" pct when percentage is null', () => {\n    const r = computeCoverage({\n      coverage_skipped: false,\n      coverage_conclusion: 'failure',\n      coverage_percentage: null,\n      coverage_baseline: null,\n    });\n    expect(r.text).toBe('❌ Coverage unknown (no baseline)');\n  });\n\n  test('shows \"no baseline\" when baseline is null but pct is known', () => {\n    const r = computeCoverage({\n      coverage_skipped: false,\n      coverage_conclusion: 'success',\n      coverage_percentage: 97.0,\n      coverage_baseline: null,\n    });\n    expect(r.text).toBe('✅ Coverage 97% (no baseline)');\n  });\n\n  // Ensure JSON string \"false\" is not coerced as boolean true\n  test('treats string \"true\" for coverage_skipped as not skipped', () => {\n    const r = computeCoverage({\n      coverage_skipped: 'true',\n      coverage_conclusion: 'success',\n      coverage_percentage: 97.0,\n      coverage_baseline: 96.0,\n    });\n    expect(r.skipped).toBe(false);\n  });\n});\n\n// ── buildCommentBody ──────────────────────────────────────────────────────────\n\ndescribe('buildCommentBody', () => {\n  const metricsText  = '✅ No significant metric changes';\n  const coverageText = '✅ Coverage 96.8% (baseline 46.4%)';\n  const footer       = '➡️ links\\n_2026-03-04 00:00:00 UTC_';\n\n  test('starts with COMMENT_TAG for idempotent find-and-update', () => {\n    const body = buildCommentBody(metricsText, coverageText, footer);\n    expect(body.startsWith(COMMENT_TAG)).toBe(true);\n  });\n\n  test('contains expected section headers', () => {\n    const body = buildCommentBody(metricsText, coverageText, footer);\n    expect(body).toContain('## CI Summary Report');\n    expect(body).toContain('### Metrics Comparison');\n    expect(body).toContain('### Code Coverage');\n  });\n\n  test('embeds metrics and coverage text', () => {\n    const body = buildCommentBody(metricsText, coverageText, footer);\n    expect(body).toContain(metricsText);\n    expect(body).toContain(coverageText);\n  });\n\n  test('ends with footer', () => {\n    const body = buildCommentBody(metricsText, coverageText, footer);\n    expect(body.endsWith(footer)).toBe(true);\n  });\n\n  test('does not include details block when no snapshots', () => {\n    const body = buildCommentBody(metricsText, coverageText, footer);\n    expect(body).not.toContain('<details>');\n  });\n\n  test('includes metrics detail when metricsSnapshots provided', () => {\n    const snapshots = [{\n      snapshot: 'cassandra_v2',\n      added: 1, removed: 0, modified: 1,\n      metric_names: ['http_server_duration'],\n    }];\n    const body = buildCommentBody('❌ 2 metric change(s) detected', coverageText, footer, { metricsSnapshots: snapshots });\n    expect(body).toContain('<details>');\n    expect(body).toContain('**cassandra_v2**');\n    expect(body).toContain('- `http_server_duration`');\n    expect(body).toContain('</details>');\n    // Verify proper section ordering\n    const metricsPos = body.indexOf('### Metrics Comparison');\n    const detailsPos = body.indexOf('<details>');\n    const coveragePos = body.indexOf('### Code Coverage');\n    expect(metricsPos).toBeLessThan(detailsPos);\n    expect(detailsPos).toBeLessThan(coveragePos);\n  });\n});\n\n// ── postCheckRun ──────────────────────────────────────────────────────────────\n\ndescribe('postCheckRun', () => {\n  const owner = 'org', repo = 'repo', headSha = 'abc123';\n\n  test('calls checks.create with correct parameters and logs result', async () => {\n    const mockGithub = {\n      rest: { checks: { create: jest.fn().mockResolvedValue({ data: { id: 42, html_url: 'https://example.com/check/42' } }) } },\n    };\n    const mockCore = { info: jest.fn() };\n\n    await postCheckRun(mockGithub, owner, repo, headSha, 'Coverage Gate', 'success',\n      { title: 'Coverage Gate', summary: '✅ ok', text: 'footer' }, mockCore);\n\n    expect(mockGithub.rest.checks.create).toHaveBeenCalledWith({\n      owner, repo, head_sha: headSha,\n      name: 'Coverage Gate',\n      status: 'completed',\n      conclusion: 'success',\n      output: { title: 'Coverage Gate', summary: '✅ ok', text: 'footer' },\n    });\n    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('Coverage Gate'));\n    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('id=42'));\n  });\n});\n\n// ── postOrUpdateComment ───────────────────────────────────────────────────────\n\ndescribe('postOrUpdateComment', () => {\n  const owner = 'org', repo = 'repo', prNumber = 99;\n  const body  = `${COMMENT_TAG}\\n## CI Summary Report`;\n\n  test('creates a new comment when none exists', async () => {\n    const mockGithub = {\n      paginate: jest.fn().mockResolvedValue([{ id: 1, body: 'unrelated comment' }]),\n      rest: { issues: {\n        listComments: jest.fn(),\n        createComment: jest.fn().mockResolvedValue({ data: { id: 201, html_url: 'https://example.com/comment/201' } }),\n      }},\n    };\n    const mockCore = { info: jest.fn() };\n\n    await postOrUpdateComment(mockGithub, owner, repo, prNumber, body, mockCore);\n\n    expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith(\n      expect.objectContaining({ owner, repo, issue_number: prNumber, body })\n    );\n    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('id=201'));\n  });\n\n  test('updates existing comment when one is found', async () => {\n    const existingComment = { id: 100, body: `${COMMENT_TAG}\\nold content` };\n    const mockGithub = {\n      paginate: jest.fn().mockResolvedValue([existingComment]),\n      rest: { issues: {\n        listComments: jest.fn(),\n        updateComment: jest.fn().mockResolvedValue({ data: { html_url: 'https://example.com/comment/100' } }),\n      }},\n    };\n    const mockCore = { info: jest.fn() };\n\n    await postOrUpdateComment(mockGithub, owner, repo, prNumber, body, mockCore);\n\n    expect(mockGithub.rest.issues.updateComment).toHaveBeenCalledWith(\n      expect.objectContaining({ owner, repo, comment_id: 100, body })\n    );\n    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('Updating existing comment id=100'));\n  });\n\n  test('skips creating a new comment when createNew is false and no existing comment', async () => {\n    const mockGithub = {\n      paginate: jest.fn().mockResolvedValue([{ id: 1, body: 'unrelated' }]),\n      rest: { issues: {\n        listComments: jest.fn(),\n        createComment: jest.fn(),\n      }},\n    };\n    const mockCore = { info: jest.fn() };\n\n    await postOrUpdateComment(mockGithub, owner, repo, prNumber, body, mockCore, { createNew: false });\n\n    expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled();\n    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('No existing comment'));\n  });\n\n  test('still updates existing comment when createNew is false', async () => {\n    const existingComment = { id: 100, body: `${COMMENT_TAG}\\nold failure` };\n    const mockGithub = {\n      paginate: jest.fn().mockResolvedValue([existingComment]),\n      rest: { issues: {\n        listComments: jest.fn(),\n        updateComment: jest.fn().mockResolvedValue({ data: { html_url: 'https://example.com/comment/100' } }),\n      }},\n    };\n    const mockCore = { info: jest.fn() };\n\n    await postOrUpdateComment(mockGithub, owner, repo, prNumber, body, mockCore, { createNew: false });\n\n    expect(mockGithub.rest.issues.updateComment).toHaveBeenCalledWith(\n      expect.objectContaining({ owner, repo, comment_id: 100, body })\n    );\n    expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining('Updating existing comment id=100'));\n  });\n});\n"
  },
  {
    "path": ".github/scripts/list-open-prs-by-author.js",
    "content": "#!/usr/bin/env node\n\n/**\n * List Open PRs Grouped by Author\n * \n * This utility script lists all open PRs in a repository grouped by author.\n * Useful for identifying which users need quota processing or backfilling.\n * \n * Usage:\n *   GITHUB_TOKEN=<token> node list-open-prs-by-author.js [owner] [repo]\n */\n\n/**\n * Fetch all open PRs grouped by author\n * @param {object} octokit - GitHub API client\n * @param {string} owner - Repository owner\n * @param {string} repo - Repository name\n * @returns {Promise<Map>} Map of author -> array of PRs\n */\nasync function fetchOpenPRsByAuthor(octokit, owner, repo) {\n  const prsByAuthor = new Map();\n  let page = 1;\n  const perPage = 100;\n\n  console.log(`📥 Fetching open PRs from ${owner}/${repo}...`);\n\n  while (true) {\n    const { data } = await octokit.rest.pulls.list({\n      owner,\n      repo,\n      state: 'open',\n      per_page: perPage,\n      page,\n      sort: 'created',\n      direction: 'asc'\n    });\n\n    if (data.length === 0) break;\n\n    for (const pr of data) {\n      const author = pr.user.login;\n      if (!prsByAuthor.has(author)) {\n        prsByAuthor.set(author, []);\n      }\n      prsByAuthor.get(author).push({\n        number: pr.number,\n        title: pr.title,\n        created_at: pr.created_at,\n        labels: pr.labels.map(l => l.name)\n      });\n    }\n\n    if (data.length < perPage) break;\n    page++;\n  }\n\n  return prsByAuthor;\n}\n\n/**\n * Display PRs grouped by author\n * @param {Map} prsByAuthor - Map of author -> PRs\n */\nfunction displayResults(prsByAuthor) {\n  // Sort by number of open PRs (descending)\n  const sortedAuthors = Array.from(prsByAuthor.entries())\n    .sort((a, b) => b[1].length - a[1].length);\n\n  console.log(`\\n📊 Found ${sortedAuthors.length} authors with open PRs\\n`);\n  console.log('=' .repeat(80));\n\n  for (const [author, prs] of sortedAuthors) {\n    const hasQuotaLabel = prs.some(pr => pr.labels.includes('pr-quota-reached'));\n    const quotaIndicator = hasQuotaLabel ? ' 🚫' : '';\n    \n    console.log(`\\n👤 @${author} (${prs.length} open PR${prs.length > 1 ? 's' : ''})${quotaIndicator}`);\n    \n    // Sort PRs by creation date (oldest first)\n    const sortedPRs = prs.sort((a, b) => \n      new Date(a.created_at) - new Date(b.created_at)\n    );\n    \n    for (const pr of sortedPRs) {\n      const date = new Date(pr.created_at).toISOString().split('T')[0];\n      const quotaLabel = pr.labels.includes('pr-quota-reached') ? ' [QUOTA REACHED]' : '';\n      console.log(`   - PR #${pr.number}: ${pr.title.substring(0, 70)}${pr.title.length > 70 ? '...' : ''}`);\n      console.log(`     Created: ${date}${quotaLabel}`);\n    }\n  }\n\n  console.log('\\n' + '='.repeat(80));\n  console.log(`\\n📋 Summary:`);\n  console.log(`   Total authors: ${sortedAuthors.length}`);\n  console.log(`   Total open PRs: ${Array.from(prsByAuthor.values()).reduce((sum, prs) => sum + prs.length, 0)}`);\n  \n  const authorsWithQuota = sortedAuthors.filter(([_, prs]) => \n    prs.some(pr => pr.labels.includes('pr-quota-reached'))\n  ).length;\n  if (authorsWithQuota > 0) {\n    console.log(`   Authors with quota-blocked PRs: ${authorsWithQuota}`);\n  }\n}\n\n/**\n * Display in CSV format for easy processing\n * @param {Map} prsByAuthor - Map of author -> PRs\n */\nfunction displayCSV(prsByAuthor) {\n  console.log('Author,PR Count,PR Numbers,Has Quota Label');\n  \n  for (const [author, prs] of prsByAuthor.entries()) {\n    const prNumbers = prs.map(pr => `#${pr.number}`).join(' ');\n    const hasQuotaLabel = prs.some(pr => pr.labels.includes('pr-quota-reached'));\n    console.log(`${author},${prs.length},\"${prNumbers}\",${hasQuotaLabel}`);\n  }\n}\n\n/**\n * Main execution function\n */\nasync function main() {\n  const args = process.argv.slice(2);\n  \n  const owner = args[0] || process.env.GITHUB_REPOSITORY?.split('/')[0] || 'jaegertracing';\n  const repo = args[1] || process.env.GITHUB_REPOSITORY?.split('/')[1] || 'jaeger';\n  const format = process.env.FORMAT || 'default'; // 'default' or 'csv'\n\n  if (!process.env.GITHUB_TOKEN) {\n    console.error('Error: GITHUB_TOKEN environment variable is required');\n    console.error('Usage: GITHUB_TOKEN=<token> node list-open-prs-by-author.js [owner] [repo]');\n    console.error('Optional: FORMAT=csv for CSV output');\n    process.exit(1);\n  }\n\n  // Import @octokit/rest dynamically\n  const { Octokit } = await import('@octokit/rest');\n  const octokit = new Octokit({\n    auth: process.env.GITHUB_TOKEN\n  });\n\n  try {\n    const prsByAuthor = await fetchOpenPRsByAuthor(octokit, owner, repo);\n    \n    if (format === 'csv') {\n      displayCSV(prsByAuthor);\n    } else {\n      displayResults(prsByAuthor);\n    }\n  } catch (error) {\n    console.error('Error:', error.message);\n    process.exit(1);\n  }\n}\n\n// Export for testing\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = {\n    fetchOpenPRsByAuthor,\n    displayResults,\n    displayCSV\n  };\n}\n\n// Run main function if executed directly\nif (require.main === module) {\n  main().catch(error => {\n    console.error('Fatal error:', error);\n    process.exit(1);\n  });\n}\n"
  },
  {
    "path": ".github/scripts/package.json",
    "content": "{\n  \"name\": \"jaeger-ci-scripts\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Jaeger CI scripts for managing pull request quotas and other automation tasks.\",\n  \"main\": \"pr-quota-manager.js\",\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"test:coverage\": \"jest --coverage\",\n    \"test:watch\": \"jest --watch\"\n  },\n  \"keywords\": [\n    \"github\",\n    \"pull-request\",\n    \"quota\",\n    \"management\"\n  ],\n  \"author\": \"Jaeger Team\",\n  \"license\": \"Apache-2.0\",\n  \"engines\": {\n    \"node\": \">=24\"\n  },\n  \"dependencies\": {\n    \"@octokit/rest\": \"^22.0.0\"\n  },\n  \"devDependencies\": {\n    \"jest\": \"30.2.0\"\n  },\n  \"jest\": {\n    \"testEnvironment\": \"node\",\n    \"coveragePathIgnorePatterns\": [\n      \"/node_modules/\"\n    ],\n    \"collectCoverageFrom\": [\n      \"pr-quota-manager.js\",\n      \"ci-summary-report-publish.js\"\n    ]\n  }\n}\n"
  },
  {
    "path": ".github/scripts/pr-quota-manager.js",
    "content": "#!/usr/bin/env node\n\n/**\n * PR Quota Management System\n * \n * This script implements a \"Waiting Room\" system that limits concurrent open PRs\n * from contributors based on their merge history, automatically unlocking queued PRs\n * when quota becomes available.\n * \n * Usage:\n *   - Via GitHub Actions (integrated with actions/github-script)\n *   - Manual execution: GITHUB_TOKEN=<token> node pr-quota-manager.js <username> [owner] [repo]\n */\n\nconst LABEL_NAME = 'pr-quota-reached';\nconst LABEL_COLOR = 'CFD3D7';\n\n/**\n * Format open/limit counts as a bullet-point status block\n * @param {number} openCount - Number of currently open PRs\n * @param {number} quota - Allowed quota\n * @returns {string} Formatted status string\n */\nfunction formatStatus(openCount, quota) {\n  return `  * Open: ${openCount}\\n  * Limit: ${quota}`;\n}\n\n/**\n * Calculate the quota for a user based on their merged PR count\n * @param {number} mergedCount - Number of merged PRs\n * @returns {number} The allowed quota\n */\nfunction calculateQuota(mergedCount) {\n  if (mergedCount === 0) return 1;\n  if (mergedCount === 1) return 2;\n  if (mergedCount === 2) return 3;\n  return 10; // Unlimited for 3+ merged PRs\n}\n\n/**\n * Fetch open and merged PRs by a specific author\n * Optimized to stop early: fetches all open PRs and only enough merged PRs to determine quota\n * @param {object} octokit - GitHub API client\n * @param {string} owner - Repository owner/org\n * @param {string} repo - Repository name\n * @param {string} author - PR author username\n * @returns {Promise<{openPRs: Array, mergedCount: number}>} Open PRs and count of merged PRs\n */\nasync function fetchAuthorPRs(octokit, owner, repo, author) {\n  const openPRs = [];\n  const mergedPRs = [];\n  const perPage = 100;\n  const MAX_MERGED_NEEDED = 3; // Stop after 3 merged PRs (gives unlimited quota)\n\n  // Fetch open PRs\n  let page = 1;\n  while (true) {\n    const { data } = await octokit.rest.pulls.list({\n      owner,\n      repo,\n      state: 'open',\n      per_page: perPage,\n      page,\n      sort: 'created',\n      direction: 'asc'\n    });\n\n    if (data.length === 0) break;\n\n    const authorPRs = data.filter(pr => pr.user.login === author);\n    openPRs.push(...authorPRs);\n\n    if (data.length < perPage) break;\n    page++;\n  }\n\n  // Fetch merged PRs, but stop once we have enough to determine quota\n  page = 1;\n  while (mergedPRs.length < MAX_MERGED_NEEDED) {\n    const { data } = await octokit.rest.pulls.list({\n      owner,\n      repo,\n      state: 'closed',\n      per_page: perPage,\n      page,\n      sort: 'created',\n      direction: 'desc' // Most recent first to find merges faster\n    });\n\n    if (data.length === 0) break;\n\n    const authorMergedPRs = data.filter(pr => pr.user.login === author && pr.merged_at !== null);\n    mergedPRs.push(...authorMergedPRs);\n\n    // Stop if we have enough merged PRs to determine unlimited quota\n    if (mergedPRs.length >= MAX_MERGED_NEEDED) break;\n    \n    if (data.length < perPage) break;\n    page++;\n  }\n\n  return {\n    openPRs,\n    mergedCount: mergedPRs.length\n  };\n}\n\n/**\n * Process quota management for a specific author\n * @param {object} octokit - GitHub API client\n * @param {string} owner - Repository owner\n * @param {string} repo - Repository name\n * @param {string} author - PR author username\n * @param {object} logger - Logger object (console or custom)\n * @param {boolean} dryRun - If true, only print actions without executing them\n * @returns {Promise<object>} Processing results\n */\nasync function processQuotaForAuthor(octokit, owner, repo, author, logger = console, dryRun = false) {\n  if (dryRun) {\n    logger.log('🔍 DRY RUN MODE - No changes will be made\\n');\n  }\n  logger.log(`\\n=== Processing Quota for: @${author} ===\\n`);\n\n  // Fetch PRs by the author (optimized to stop early)\n  const { openPRs, mergedCount } = await fetchAuthorPRs(octokit, owner, repo, author);\n\n  // Open PRs are already sorted by creation date (oldest first) from the fetch\n  const quota = calculateQuota(mergedCount);\n  const openCount = openPRs.length;\n\n  // Log history audit\n  logger.log('📜 History Audit:');\n  if (mergedCount === 0) {\n    logger.log('  No merged PRs found.');\n  } else if (mergedCount >= 3) {\n    logger.log(`  User has ${mergedCount}+ merged PRs (unlimited quota).`);\n  } else {\n    logger.log(`  User has ${mergedCount} merged PR${mergedCount > 1 ? 's' : ''}.`);\n  }\n\n  // Log current stats\n  logger.log(`\\n📊 Current Stats:`);\n  logger.log(`  User has ${mergedCount} merged PRs. Current Quota: ${quota}. Currently Open: ${openCount}.`);\n\n  // Ensure label exists\n  if (!dryRun) {\n    await ensureLabelExists(octokit, owner, repo, logger);\n  }\n\n  // Process each open PR\n  const results = {\n    blocked: [],\n    unblocked: [],\n    unchanged: []\n  };\n\n  logger.log(`\\n🔄 Processing Open PRs:\\n`);\n\n  for (let i = 0; i < openPRs.length; i++) {\n    const pr = openPRs[i];\n    const shouldBeBlocked = i >= quota;\n    const isCurrentlyBlocked = pr.labels.some(label => label.name === LABEL_NAME);\n\n    if (shouldBeBlocked && !isCurrentlyBlocked) {\n      // Need to block this PR\n      if (dryRun) {\n        logger.log(`  🔍 [DRY RUN] Would label PR #${pr.number} as blocked (Position: ${i + 1}/${openCount}, Quota: ${quota})`);\n        logger.log(`  🔍 [DRY RUN] Would post blocking comment on PR #${pr.number}`);\n      } else {\n        await addLabel(octokit, owner, repo, pr.number, logger);\n        await postBlockingComment(octokit, owner, repo, pr.number, author, openCount, quota, logger);\n        logger.log(`  ✅ Labeled PR #${pr.number} as blocked (Position: ${i + 1}/${openCount}, Quota: ${quota})`);\n      }\n      results.blocked.push(pr.number);\n    } else if (!shouldBeBlocked && isCurrentlyBlocked) {\n      // Need to unblock this PR\n      if (dryRun) {\n        logger.log(`  🔍 [DRY RUN] Would remove label from PR #${pr.number} (Position: ${i + 1}/${openCount}, Quota: ${quota})`);\n        logger.log(`  🔍 [DRY RUN] Would post unblocking comment on PR #${pr.number}`);\n      } else {\n        await removeLabel(octokit, owner, repo, pr.number, logger);\n        await postUnblockingComment(octokit, owner, repo, pr.number, author, openCount, quota, logger);\n        logger.log(`  ✅ Unblocked PR #${pr.number} (Position: ${i + 1}/${openCount}, Quota: ${quota})`);\n      }\n      results.unblocked.push(pr.number);\n    } else {\n      results.unchanged.push(pr.number);\n      logger.log(`  ℹ️  PR #${pr.number} unchanged (${shouldBeBlocked ? 'blocked' : 'active'})`);\n    }\n  }\n\n  logger.log(`\\n✅ Processing Complete for @${author}\\n`);\n\n  return {\n    author,\n    mergedCount,\n    quota,\n    openCount,\n    results\n  };\n}\n\n/**\n * Ensure the pr-quota-reached label exists in the repository\n */\nasync function ensureLabelExists(octokit, owner, repo, logger) {\n  try {\n    await octokit.rest.issues.getLabel({\n      owner,\n      repo,\n      name: LABEL_NAME\n    });\n  } catch (error) {\n    if (error.status === 404) {\n      logger.log(`🏷️  Creating label: ${LABEL_NAME}`);\n      await octokit.rest.issues.createLabel({\n        owner,\n        repo,\n        name: LABEL_NAME,\n        color: LABEL_COLOR,\n        description: 'PR is on hold due to quota limits for new contributors'\n      });\n    } else {\n      throw error;\n    }\n  }\n}\n\n/**\n * Add the quota-reached label to a PR\n */\nasync function addLabel(octokit, owner, repo, issueNumber, logger) {\n  try {\n    await octokit.rest.issues.addLabels({\n      owner,\n      repo,\n      issue_number: issueNumber,\n      labels: [LABEL_NAME]\n    });\n  } catch (error) {\n    logger.error(`Failed to add label to PR #${issueNumber}:`, error.message);\n  }\n}\n\n/**\n * Remove the quota-reached label from a PR\n */\nasync function removeLabel(octokit, owner, repo, issueNumber, logger) {\n  try {\n    await octokit.rest.issues.removeLabel({\n      owner,\n      repo,\n      issue_number: issueNumber,\n      name: LABEL_NAME\n    });\n  } catch (error) {\n    // Ignore 404 errors (label wasn't present)\n    if (error.status !== 404) {\n      logger.error(`Failed to remove label from PR #${issueNumber}:`, error.message);\n    }\n  }\n}\n\n/**\n * Check if a blocking comment already exists on the PR\n */\nasync function hasBlockingComment(octokit, owner, repo, issueNumber) {\n  const { data: comments } = await octokit.rest.issues.listComments({\n    owner,\n    repo,\n    issue_number: issueNumber\n  });\n\n  return comments.some(comment => \n    comment.body && comment.body.includes('This PR is currently **on hold**')\n  );\n}\n\n\n\n/**\n * Post a blocking comment to a PR\n */\nasync function postBlockingComment(octokit, owner, repo, issueNumber, author, openCount, quota, logger) {\n  // Check if blocking comment already exists\n  if (await hasBlockingComment(octokit, owner, repo, issueNumber)) {\n    logger.log(`  ℹ️  Blocking comment already exists on PR #${issueNumber}, skipping.`);\n    return;\n  }\n\n  const message = `Hi @${author}, thanks for your contribution! To ensure quality reviews, we limit how many concurrent PRs new contributors can open:\n${formatStatus(openCount, quota)}\n\nThis PR is currently **on hold**. We will automatically move this into the review queue once your existing PRs are merged or closed.\n\nPlease see our [Contributing Guidelines](https://github.com/jaegertracing/jaeger/blob/main/CONTRIBUTING_GUIDELINES.md#pull-request-limits-for-new-contributors) for details on our tiered quota policy.`;\n\n  try {\n    await octokit.rest.issues.createComment({\n      owner,\n      repo,\n      issue_number: issueNumber,\n      body: message\n    });\n  } catch (error) {\n    logger.error(`Failed to post blocking comment on PR #${issueNumber}:`, error.message);\n  }\n}\n\n/**\n * Post an unblocking comment to a PR\n * Always posts when called - if PR was blocked again after being unblocked, user should be notified again\n */\nasync function postUnblockingComment(octokit, owner, repo, issueNumber, author, openCount, quota, logger) {\n  const message = `PR quota unlocked!\n\n@${author}, this PR has been moved out of the waiting room and into the active review queue:\n${formatStatus(openCount, quota)}\n\nThank you for your patience.`;\n\n  try {\n    await octokit.rest.issues.createComment({\n      owner,\n      repo,\n      issue_number: issueNumber,\n      body: message\n    });\n  } catch (error) {\n    logger.error(`Failed to post unblocking comment on PR #${issueNumber}:`, error.message);\n  }\n}\n\n/**\n * Main execution function for manual CLI usage\n */\nasync function main() {\n  const args = process.argv.slice(2);\n  \n  if (args.length < 1) {\n    console.error('Usage: GITHUB_TOKEN=<token> node pr-quota-manager.js <username> [owner] [repo]');\n    process.exit(1);\n  }\n\n  const username = args[0];\n  const owner = args[1] || process.env.GITHUB_REPOSITORY?.split('/')[0] || 'jaegertracing';\n  const repo = args[2] || process.env.GITHUB_REPOSITORY?.split('/')[1] || 'jaeger';\n  const dryRun = process.env.DRY_RUN === 'true' || args.includes('--dry-run');\n\n  if (!process.env.GITHUB_TOKEN) {\n    console.error('Error: GITHUB_TOKEN environment variable is required');\n    process.exit(1);\n  }\n\n  // Import @octokit/rest dynamically for CLI usage\n  const { Octokit } = await import('@octokit/rest');\n  const octokit = new Octokit({\n    auth: process.env.GITHUB_TOKEN\n  });\n\n  try {\n    const result = await processQuotaForAuthor(octokit, owner, repo, username, console, dryRun);\n    console.log('\\n📋 Summary:');\n    console.log(`  - Blocked: ${result.results.blocked.length} PRs`);\n    console.log(`  - Unblocked: ${result.results.unblocked.length} PRs`);\n    console.log(`  - Unchanged: ${result.results.unchanged.length} PRs`);\n  } catch (error) {\n    console.error('Error:', error.message);\n    process.exit(1);\n  }\n}\n\n// GitHub Actions wrapper function\nasync function githubActionHandler({github, core, username, owner, repo, dryRun = false}) {\n  if (!username) {\n    core.setFailed('Username is required');\n    return;\n  }\n  \n  if (!owner || !repo) {\n    core.setFailed('Owner and repo are required');\n    return;\n  }\n  \n  // Process the quota\n  try {\n    const result = await processQuotaForAuthor(github, owner, repo, username, console, dryRun);\n    \n    core.info('');\n    core.info('=== Summary ===');\n    core.info(`Blocked: ${result.results.blocked.length} PRs`);\n    core.info(`Unblocked: ${result.results.unblocked.length} PRs`);\n    core.info(`Unchanged: ${result.results.unchanged.length} PRs`);\n    \n    if (result.results.blocked.length > 0) {\n      core.info(`Blocked PRs: ${result.results.blocked.join(', ')}`);\n    }\n    \n    if (result.results.unblocked.length > 0) {\n      core.info(`Unblocked PRs: ${result.results.unblocked.join(', ')}`);\n    }\n  } catch (error) {\n    core.setFailed(`Error processing quota: ${error.message}`);\n    throw error;\n  }\n}\n\n// Export for GitHub Actions usage\nif (typeof module !== 'undefined' && module.exports) {\n  // Default export is the GitHub Actions handler\n  module.exports = githubActionHandler;\n  \n  // Named exports for testing and direct usage\n  module.exports.formatStatus = formatStatus;\n  module.exports.calculateQuota = calculateQuota;\n  module.exports.fetchAuthorPRs = fetchAuthorPRs;\n  module.exports.processQuotaForAuthor = processQuotaForAuthor;\n  module.exports.ensureLabelExists = ensureLabelExists;\n  module.exports.addLabel = addLabel;\n  module.exports.removeLabel = removeLabel;\n  module.exports.hasBlockingComment = hasBlockingComment;\n  module.exports.postBlockingComment = postBlockingComment;\n  module.exports.postUnblockingComment = postUnblockingComment;\n  module.exports.LABEL_NAME = LABEL_NAME;\n  module.exports.LABEL_COLOR = LABEL_COLOR;\n}\n\n// Run main function if executed directly\nif (require.main === module) {\n  main().catch(error => {\n    console.error('Fatal error:', error);\n    process.exit(1);\n  });\n}\n"
  },
  {
    "path": ".github/scripts/pr-quota-manager.test.js",
    "content": "/**\n * Unit tests for PR Quota Management System\n */\n\nconst prQuotaManager = require('./pr-quota-manager');\nconst {\n  formatStatus,\n  calculateQuota,\n  fetchAuthorPRs,\n  processQuotaForAuthor,\n  ensureLabelExists,\n  addLabel,\n  removeLabel,\n  hasBlockingComment,\n  postBlockingComment,\n  postUnblockingComment,\n  LABEL_NAME,\n  LABEL_COLOR\n} = prQuotaManager;\n\n// Mock logger to suppress output during tests\nconst mockLogger = {\n  log: jest.fn(),\n  error: jest.fn()\n};\n\ndescribe('formatStatus', () => {\n  test('formats open count and quota as bullet points', () => {\n    expect(formatStatus(3, 5)).toBe('  * Open: 3\\n  * Limit: 5');\n  });\n});\n\ndescribe('calculateQuota', () => {\n  test('returns 1 for 0 merged PRs', () => {\n    expect(calculateQuota(0)).toBe(1);\n  });\n\n  test('returns 2 for 1 merged PR', () => {\n    expect(calculateQuota(1)).toBe(2);\n  });\n\n  test('returns 3 for 2 merged PRs', () => {\n    expect(calculateQuota(2)).toBe(3);\n  });\n\n  test('returns 10 (unlimited) for 3 merged PRs', () => {\n    expect(calculateQuota(3)).toBe(10);\n  });\n\n  test('returns 10 (unlimited) for 10 merged PRs', () => {\n    expect(calculateQuota(10)).toBe(10);\n  });\n});\n\ndescribe('fetchAuthorPRs', () => {\n  test('fetches open PRs and merged count', async () => {\n    const mockOctokit = {\n      rest: {\n        pulls: {\n          list: jest.fn()\n            // First call for open PRs\n            .mockResolvedValueOnce({\n              data: [\n                { number: 1, user: { login: 'testuser' }, state: 'open', merged_at: null },\n                { number: 2, user: { login: 'otheruser' }, state: 'open', merged_at: null },\n                { number: 3, user: { login: 'testuser' }, state: 'open', merged_at: null }\n              ]\n            })\n            // Second call for closed/merged PRs\n            .mockResolvedValueOnce({\n              data: [\n                { number: 10, user: { login: 'testuser' }, merged_at: '2024-01-01' },\n                { number: 11, user: { login: 'otheruser' }, merged_at: '2024-01-02' }\n              ]\n            })\n        }\n      }\n    };\n\n    const result = await fetchAuthorPRs(mockOctokit, 'owner', 'repo', 'testuser');\n\n    expect(result.openPRs).toHaveLength(2);\n    expect(result.openPRs[0].number).toBe(1);\n    expect(result.openPRs[1].number).toBe(3);\n    expect(result.mergedCount).toBe(1);\n  });\n\n  test('stops fetching merged PRs after finding 3', async () => {\n    const mockOctokit = {\n      rest: {\n        pulls: {\n          list: jest.fn()\n            // Open PRs call\n            .mockResolvedValueOnce({ data: [] })\n            // First batch of closed PRs with 3 merged\n            .mockResolvedValueOnce({\n              data: [\n                { number: 1, user: { login: 'testuser' }, merged_at: '2024-01-01' },\n                { number: 2, user: { login: 'testuser' }, merged_at: '2024-01-02' },\n                { number: 3, user: { login: 'testuser' }, merged_at: '2024-01-03' },\n                { number: 4, user: { login: 'testuser' }, merged_at: null }, // closed but not merged\n              ]\n            })\n        }\n      }\n    };\n\n    const result = await fetchAuthorPRs(mockOctokit, 'owner', 'repo', 'testuser');\n\n    expect(result.mergedCount).toBe(3);\n    // Should stop after finding 3 merged PRs, so only 2 calls (1 for open, 1 for closed)\n    expect(mockOctokit.rest.pulls.list).toHaveBeenCalledTimes(2);\n  });\n});\n\ndescribe('ensureLabelExists', () => {\n  test('does not create label if it already exists', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          getLabel: jest.fn().mockResolvedValue({ data: { name: LABEL_NAME } }),\n          createLabel: jest.fn()\n        }\n      }\n    };\n\n    await ensureLabelExists(mockOctokit, 'owner', 'repo', mockLogger);\n\n    expect(mockOctokit.rest.issues.getLabel).toHaveBeenCalledWith({\n      owner: 'owner',\n      repo: 'repo',\n      name: LABEL_NAME\n    });\n    expect(mockOctokit.rest.issues.createLabel).not.toHaveBeenCalled();\n  });\n\n  test('creates label if it does not exist', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          getLabel: jest.fn().mockRejectedValue({ status: 404 }),\n          createLabel: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    await ensureLabelExists(mockOctokit, 'owner', 'repo', mockLogger);\n\n    expect(mockOctokit.rest.issues.createLabel).toHaveBeenCalledWith({\n      owner: 'owner',\n      repo: 'repo',\n      name: LABEL_NAME,\n      color: LABEL_COLOR,\n      description: 'PR is on hold due to quota limits for new contributors'\n    });\n  });\n});\n\ndescribe('addLabel', () => {\n  test('adds label to PR', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          addLabels: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    await addLabel(mockOctokit, 'owner', 'repo', 123, mockLogger);\n\n    expect(mockOctokit.rest.issues.addLabels).toHaveBeenCalledWith({\n      owner: 'owner',\n      repo: 'repo',\n      issue_number: 123,\n      labels: [LABEL_NAME]\n    });\n  });\n\n  test('handles errors gracefully', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          addLabels: jest.fn().mockRejectedValue(new Error('API error'))\n        }\n      }\n    };\n\n    await addLabel(mockOctokit, 'owner', 'repo', 123, mockLogger);\n\n    expect(mockLogger.error).toHaveBeenCalledWith(\n      expect.stringContaining('Failed to add label'),\n      expect.any(String)\n    );\n  });\n});\n\ndescribe('removeLabel', () => {\n  test('removes label from PR', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          removeLabel: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    await removeLabel(mockOctokit, 'owner', 'repo', 123, mockLogger);\n\n    expect(mockOctokit.rest.issues.removeLabel).toHaveBeenCalledWith({\n      owner: 'owner',\n      repo: 'repo',\n      issue_number: 123,\n      name: LABEL_NAME\n    });\n  });\n\n  test('ignores 404 errors when label is not present', async () => {\n    const testLogger = {\n      log: jest.fn(),\n      error: jest.fn()\n    };\n    \n    const mockOctokit = {\n      rest: {\n        issues: {\n          removeLabel: jest.fn().mockRejectedValue({ status: 404 })\n        }\n      }\n    };\n\n    await removeLabel(mockOctokit, 'owner', 'repo', 123, testLogger);\n\n    expect(testLogger.error).not.toHaveBeenCalled();\n  });\n\n  test('logs non-404 errors', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          removeLabel: jest.fn().mockRejectedValue({ status: 500, message: 'Server error' })\n        }\n      }\n    };\n\n    await removeLabel(mockOctokit, 'owner', 'repo', 123, mockLogger);\n\n    expect(mockLogger.error).toHaveBeenCalled();\n  });\n});\n\ndescribe('hasBlockingComment', () => {\n  test('returns true if blocking comment exists', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          listComments: jest.fn().mockResolvedValue({\n            data: [\n              { body: 'Some other comment' },\n              { body: 'This PR is currently **on hold**' }\n            ]\n          })\n        }\n      }\n    };\n\n    const result = await hasBlockingComment(mockOctokit, 'owner', 'repo', 123);\n\n    expect(result).toBe(true);\n  });\n\n  test('returns false if blocking comment does not exist', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          listComments: jest.fn().mockResolvedValue({\n            data: [\n              { body: 'Some other comment' },\n              { body: 'Another comment' }\n            ]\n          })\n        }\n      }\n    };\n\n    const result = await hasBlockingComment(mockOctokit, 'owner', 'repo', 123);\n\n    expect(result).toBe(false);\n  });\n});\n\n\n\ndescribe('postBlockingComment', () => {\n  test('posts blocking comment if none exists', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          listComments: jest.fn().mockResolvedValue({ data: [] }),\n          createComment: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    await postBlockingComment(mockOctokit, 'owner', 'repo', 123, 'testuser', 2, 1, mockLogger);\n\n    expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledWith({\n      owner: 'owner',\n      repo: 'repo',\n      issue_number: 123,\n      body: expect.stringContaining('This PR is currently **on hold**')\n    });\n  });\n\n  test('skips comment if blocking comment already exists', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          listComments: jest.fn().mockResolvedValue({\n            data: [{ body: 'This PR is currently **on hold**' }]\n          }),\n          createComment: jest.fn()\n        }\n      }\n    };\n\n    await postBlockingComment(mockOctokit, 'owner', 'repo', 123, 'testuser', 2, 1, mockLogger);\n\n    expect(mockOctokit.rest.issues.createComment).not.toHaveBeenCalled();\n  });\n});\n\ndescribe('postUnblockingComment', () => {\n  test('always posts unblocking comment', async () => {\n    const mockOctokit = {\n      rest: {\n        issues: {\n          createComment: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    await postUnblockingComment(mockOctokit, 'owner', 'repo', 123, 'testuser', 1, 2, mockLogger);\n\n    expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledWith({\n      owner: 'owner',\n      repo: 'repo',\n      issue_number: 123,\n      body: expect.stringContaining('PR quota unlocked!')\n    });\n  });\n});\n\ndescribe('processQuotaForAuthor', () => {\n  test('blocks PRs exceeding quota for new contributor', async () => {\n    const mockOctokit = {\n      rest: {\n        pulls: {\n          list: jest.fn()\n            // Open PRs call\n            .mockResolvedValueOnce({\n              data: [\n                { \n                  number: 1, \n                  user: { login: 'newuser' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-01T00:00:00Z',\n                  labels: []\n                },\n                { \n                  number: 2, \n                  user: { login: 'newuser' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-02T00:00:00Z',\n                  labels: []\n                }\n              ]\n            })\n            // Closed PRs call (no merged PRs found)\n            .mockResolvedValueOnce({ data: [] })\n        },\n        issues: {\n          getLabel: jest.fn().mockResolvedValue({ data: { name: LABEL_NAME } }),\n          addLabels: jest.fn().mockResolvedValue({}),\n          listComments: jest.fn().mockResolvedValue({ data: [] }),\n          createComment: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    const result = await processQuotaForAuthor(mockOctokit, 'owner', 'repo', 'newuser', mockLogger);\n\n    expect(result.mergedCount).toBe(0);\n    expect(result.quota).toBe(1);\n    expect(result.openCount).toBe(2);\n    expect(result.results.blocked).toEqual([2]);\n    expect(result.results.unchanged).toEqual([1]);\n  });\n\n  test('unblocks PRs when quota becomes available', async () => {\n    const mockOctokit = {\n      rest: {\n        pulls: {\n          list: jest.fn()\n            // Open PRs call\n            .mockResolvedValueOnce({\n              data: [\n                { \n                  number: 1, \n                  user: { login: 'contributor' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-01T00:00:00Z',\n                  labels: []\n                },\n                { \n                  number: 3, \n                  user: { login: 'contributor' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-03T00:00:00Z',\n                  labels: [{ name: LABEL_NAME }]\n                }\n              ]\n            })\n            // Closed PRs call (1 merged)\n            .mockResolvedValueOnce({\n              data: [\n                { \n                  number: 2, \n                  user: { login: 'contributor' }, \n                  merged_at: '2024-01-05T00:00:00Z'\n                }\n              ]\n            })\n        },\n        issues: {\n          getLabel: jest.fn().mockResolvedValue({ data: { name: LABEL_NAME } }),\n          removeLabel: jest.fn().mockResolvedValue({}),\n          listComments: jest.fn().mockResolvedValue({ data: [] }),\n          createComment: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    const result = await processQuotaForAuthor(mockOctokit, 'owner', 'repo', 'contributor', mockLogger);\n\n    expect(result.mergedCount).toBe(1);\n    expect(result.quota).toBe(2);\n    expect(result.openCount).toBe(2);\n    expect(result.results.unblocked).toEqual([3]);\n  });\n\n  test('processes PRs in order by creation date (oldest first)', async () => {\n    const mockOctokit = {\n      rest: {\n        pulls: {\n          list: jest.fn()\n            // Open PRs are already sorted by creation date from the API\n            .mockResolvedValueOnce({\n              data: [\n                { \n                  number: 1, \n                  user: { login: 'user' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-01T00:00:00Z',\n                  labels: []\n                },\n                { \n                  number: 2, \n                  user: { login: 'user' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-02T00:00:00Z',\n                  labels: []\n                },\n                { \n                  number: 3, \n                  user: { login: 'user' }, \n                  state: 'open', \n                  merged_at: null,\n                  created_at: '2024-01-03T00:00:00Z',\n                  labels: []\n                }\n              ]\n            })\n            // No merged PRs\n            .mockResolvedValueOnce({ data: [] })\n        },\n        issues: {\n          getLabel: jest.fn().mockResolvedValue({ data: { name: LABEL_NAME } }),\n          addLabels: jest.fn().mockResolvedValue({}),\n          listComments: jest.fn().mockResolvedValue({ data: [] }),\n          createComment: jest.fn().mockResolvedValue({})\n        }\n      }\n    };\n\n    const result = await processQuotaForAuthor(mockOctokit, 'owner', 'repo', 'user', mockLogger);\n\n    // First PR (oldest) should not be blocked, others should be\n    expect(result.results.unchanged).toEqual([1]);\n    expect(result.results.blocked).toEqual([2, 3]);\n  });\n});\n"
  },
  {
    "path": ".github/scripts/waiting-for-author.js",
    "content": "module.exports = async ({github, context, core}) => {\n  const LABEL_NAME = 'waiting-for-author';\n\n  // Determine event type\n  const eventName = context.eventName;\n  \n  // Get PR data\n  let prNumber;\n  let repoOwner;\n  let repoName;\n\n  if (eventName === 'issue_comment') {\n    prNumber = context.payload.issue.number;\n    repoOwner = context.repo.owner;\n    repoName = context.repo.repo;\n  } else if (eventName === 'pull_request_target') {\n    prNumber = context.payload.number;\n    repoOwner = context.repo.owner;\n    repoName = context.repo.repo;\n  } else {\n    core.info(`Unsupported event: ${eventName}`);\n    return;\n  }\n\n  // Fetch PR details to get the author\n  // We need to fetch the PR object because issue_comment payload doesn't always have full PR details (like author)\n  // correctly populated in a way that is identical to pull_request payload for our needs.\n  const { data: pr } = await github.rest.pulls.get({\n    owner: repoOwner,\n    repo: repoName,\n    pull_number: prNumber,\n  });\n\n  const prAuthor = pr.user.login;\n\n  if (eventName === 'issue_comment') {\n    const commenter = context.payload.comment.user.login;\n    \n    // Logic:\n    // If Maintainer comments -> Add label (if not present)\n    // If Author comments -> Remove label (if present)\n    \n    // Check if commenter is the author\n    if (commenter === prAuthor) {\n      core.info(`Comment by author ${commenter}. Removing label if present.`);\n      await removeLabel(github, repoOwner, repoName, prNumber, LABEL_NAME, core);\n    } \n    // Check if commenter is a maintainer (has write access)\n    else if (await isMaintainer(github, repoOwner, repoName, commenter, core)) {\n       core.info(`Comment by maintainer ${commenter}. Adding label if missing.`);\n       await addLabel(github, repoOwner, repoName, prNumber, LABEL_NAME, core);\n    } else {\n      core.info(`Comment by ${commenter} (not author or maintainer). No action taken.`);\n    }\n\n  } else if (eventName === 'pull_request_target') {\n    // This is the 'synchronize' event (push to PR branch)\n    // Logic:\n    // If Author pushes -> Remove label (UNLESS it's just an \"Update branch\" merge)\n    \n    const sender = context.payload.sender.login;\n    if (sender !== prAuthor) {\n       core.info(`Push by ${sender}, not the PR author ${prAuthor}. Doing nothing.`);\n       return;\n    }\n\n    // Check if it's an \"Update branch\" commit\n    // We look at the commits in this push.\n    // context.payload.before and context.payload.after give us the range of commits.\n    // simpler approach: look at the head commit of the PR.\n    \n    // Ideally we want to see if the content changed or if it was just a merge from base.\n    // A heuristic is to check the commit message or parents of the head commit.\n    \n    // We'll fetch the commit details.\n    const headSha = context.payload.pull_request.head.sha;\n    const { data: commit } = await github.rest.repos.getCommit({\n      owner: repoOwner,\n      repo: repoName,\n      ref: headSha,\n    });\n\n    const message = commit.commit.message;\n    const parents = commit.parents;\n\n    // A merge commit typically has 2 parents.\n    // If it's a merge from the base branch (e.g. \"Merge branch 'main' into ...\")\n    // Note: GitHub's \"Update branch\" button creates a merge commit.\n    \n    const isMergeCommit = parents.length > 1;\n    const isUpdateBranch = isMergeCommit && (\n        message.startsWith(`Merge branch '${context.payload.pull_request.base.ref}'`) || \n        message.startsWith(`Merge remote-tracking branch 'origin/${context.payload.pull_request.base.ref}'`)\n    );\n\n    if (isUpdateBranch) {\n      core.info(`Push detected as 'Update branch' (Merge from base). Keeping label.`);\n      return;\n    }\n\n    core.info(`Push by author detected. Removing label.`);\n    await removeLabel(github, repoOwner, repoName, prNumber, LABEL_NAME, core);\n  }\n\n};\n\nasync function addLabel(github, owner, repo, issueNumber, label, logger) {\n  try {\n    const { data: labels } = await github.rest.issues.listLabelsOnIssue({\n      owner,\n      repo,\n      issue_number: issueNumber,\n    });\n    \n    if (labels.find(l => l.name === label)) {\n      logger.info(`Label '${label}' already exists.`);\n      return;\n    }\n\n    await github.rest.issues.addLabels({\n      owner,\n      repo,\n      issue_number: issueNumber,\n      labels: [label],\n    });\n    logger.info(`Added label '${label}'.`);\n  } catch (error) {\n    logger.error(`Error adding label: ${error.message}`);\n  }\n}\n\nasync function removeLabel(github, owner, repo, issueNumber, label, logger) {\n  try {\n    const { data: labels } = await github.rest.issues.listLabelsOnIssue({\n      owner,\n      repo,\n      issue_number: issueNumber,\n    });\n    \n    if (!labels.find(l => l.name === label)) {\n      logger.info(`Label '${label}' does not exist.`);\n      return;\n    }\n\n    await github.rest.issues.removeLabel({\n      owner,\n      repo,\n      issue_number: issueNumber,\n      name: label,\n    });\n    logger.info(`Removed label '${label}'.`);\n  } catch (error) {\n    // Ignore 404 if label not found (though check above should catch it)\n    logger.error(`Error removing label: ${error.message}`);\n  }\n}\n\nasync function isMaintainer(github, owner, repo, username, logger) {\n  try {\n    const { data } = await github.rest.repos.getCollaboratorPermissionLevel({\n      owner,\n      repo,\n      username,\n    });\n    \n    // Based on gh api logic: .permissions.maintain==true or .permissions.admin==true or .permissions.push==true\n    // getCollaboratorPermissionLevel returns a 'permission' field which describes the permission level.\n    // Levels: 'admin', 'maintain', 'write', 'triage', 'read', 'none'\n    // We want 'admin', 'maintain', or 'write'.\n    const permission = data.permission;\n    return ['admin', 'maintain', 'write'].includes(permission);\n  } catch (error) {\n    logger.error(`Error checking permissions for ${username}: ${error.message}`);\n    return false;\n  }\n}\n"
  },
  {
    "path": ".github/workflows/README.md",
    "content": "# CI Workflows\n\nThis directory contains GitHub Actions workflows for the Jaeger project. The workflows are organized into a staged architecture to optimize CI resource usage and provide fail-fast behavior.\n\n## Architecture Overview\n\nThe CI system uses a **Forked DAG (Directed Acyclic Graph)** orchestrated by `ci-orchestrator.yml`. The orchestrator supports two execution paths based on the context of the run:\n\n- **Sequential path (~30m)**: Default for external contributors. Stage 1 must pass before Stage 2, and Stage 2 must pass before Stage 3. Provides fail-fast behavior that saves resources when linting or unit tests fail.\n- **Parallel path (~10m)**: For trusted maintainers, merge queue, and main branch builds. All three stages start simultaneously after a setup step.\n\n### CI Orchestrator\n\nThe main entry point for PR and branch CI is **`ci-orchestrator.yml`**, which:\n1. Runs a **`setup`** job to determine the execution mode (parallel or sequential)\n2. Triggers either the sequential or parallel path based on the result\n\n#### Setup Job: Execution Mode Detection\n\nThe `setup` job determines whether to use parallel execution based on these **OR** conditions:\n\n| Condition | Rationale |\n|-----------|-----------|\n| Push to `main` branch | Already merged, fully trusted |\n| `merge_group` event | Merge Queue entry, high confidence |\n| PR author is an org member (`MEMBER` or `OWNER`) | Trusted maintainer |\n| PR author login is `dependabot[bot]` or `renovate-bot` | Dependency automation bots |\n| PR has the `ci:parallel` label | Explicit opt-in |\n\n#### Stage Workflows (DRY Encapsulation)\n\nEach stage is encapsulated in a reusable \"stage\" workflow:\n\n- **ci-orchestrator-stage1.yml** - Stage 1 workflows (Linters only — fast fail-fast gate)\n- **ci-orchestrator-stage2.yml** - Stage 2 workflows (Unit Tests)\n- **ci-orchestrator-stage3.yml** - Stage 3 workflows (Docker, E2E, Binaries, Static Analysis)\n\nThis avoids duplication: both the sequential and parallel paths call the same stage workflows.\n\n#### Stage 1: Fast Gate (Linters only)\n- **ci-lint-checks.yaml** - Go linting, DCO checks, generated files validation, shell script linting\n\n#### Stage 2: Unit Tests\n- **ci-unit-tests.yml** - Full unit test suite with coverage\n\n#### Stage 3: Expensive Checks & Static Analysis\nExecutes in parallel within the stage:\n- **ci-build-binaries.yml** - Multi-platform binary builds\n- **ci-docker-build.yml** - Docker images for all components\n- **ci-docker-all-in-one.yml** - All-in-one Docker image\n- **ci-docker-hotrod.yml** - HotROD demo application image\n- **ci-e2e-all.yml** - E2E test suite orchestrator (calls individual E2E workflows)\n- **ci-e2e-spm.yml** - Service Performance Monitoring tests\n- **ci-e2e-tailsampling.yml** - Tail sampling processor tests\n- **codeql.yml** - Security scanning with CodeQL\n- **dependency-review.yml** - Dependency vulnerability checks\n- **fossa.yml** - License compliance scanning\n\n#### Gatekeeper Job\nThe orchestrator includes a final **`ci-success`** job that:\n- Runs after all stage jobs (regardless of which path was taken)\n- Determines which path was used and validates its results\n- Should be used as the required status check in GitHub branch protection rules\n\n### Execution Flow Diagram\n\n```\n                         ┌─────────┐\n                         │  setup  │\n                         └────┬────┘\n              parallel=false  │  parallel=true\n           ┌──────────────────┴──────────────────┐\n           │  Sequential Path                     │  Parallel Path\n           │                                      │\n      ┌────▼──────┐                  ┌────────────┼────────────┐\n      │ stage1-seq│                  │            │            │\n      └────┬──────┘             ┌────▼───┐  ┌────▼───┐  ┌────▼───┐\n           │                    │stage1- │  │stage2- │  │stage3- │\n      ┌────▼──────┐             │  fast  │  │  fast  │  │  fast  │\n      │ stage2-seq│             └────┬───┘  └────┬───┘  └────┬───┘\n      └────┬──────┘                  │            │           │\n           │                         └────────────┴───────────┘\n      ┌────▼──────┐                              │\n      │ stage3-seq│                              │\n      └────┬──────┘                              │\n           └──────────────────┬──────────────────┘\n                         ┌────▼────┐\n                         │ci-success│\n                         └─────────┘\n```\n\n### Concurrency Control\n\nThe orchestrator manages concurrency centrally:\n```yaml\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n```\n\nThis allows a single \"kill-switch\" to cancel older runs when new commits are pushed to a PR.\n\n### Permissions Model\n\nThe orchestrator uses `permissions: write-all` to allow maximum flexibility for child workflows:\n\n```yaml\npermissions: write-all\n```\n\nThis grants broad permissions at the orchestrator level, allowing child workflows to request the specific permissions they need. Child workflows then apply the principle of least privilege by downgrading to only the permissions they require:\n\n- **codeql.yml**: `security-events: write`, `actions: read` (for security scanning)\n- **ci-unit-tests.yml**: `checks: write` (for reporting test results)\n- **ci-docker-all-in-one.yml**: `packages: read` (for pulling from GHCR)\n- Other workflows: typically `contents: read` only\n\n**Why write-all?** When using `workflow_call`, GitHub Actions requires the caller workflow to grant permissions that called workflows can then use or downgrade. Without `write-all`, child workflows would be restricted to `contents: read` only, causing failures for workflows that need additional permissions like CodeQL or test reporting.\n\n## Independent Workflows\n\nThe following workflows operate independently and are **not** part of the orchestrator:\n\n### Release & Deployment\n- **ci-release.yml** - Triggered on release events to build and publish artifacts\n- **ci-deploy-demo.yml** - Scheduled/manual deployment to demo environment\n\n### Automated Checks\n- **ci-summary-report.yml** - Fan-in workflow triggered after CI Orchestrator completes; posts a consolidated PR comment with performance metrics comparison and code coverage gating (see `docs/adr/004-migrating-coverage-gating-to-github-actions.md`)\n- **label-check.yml** - Verifies PR labels\n- **pr-quota-manager.yml** - PR management automation\n- **dco_merge_group.yml** - DCO verification for merge groups\n\n### Scheduled Maintenance\n- **stale.yml** - Marks and closes stale issues/PRs\n- **waiting-for-author.yml** - PR status management\n- **scorecard.yml** - Security scorecard scanning\n\n### Special Cases\n- **ci-unit-tests-go-tip.yml** - Tests against Go development version (runs on main or when workflow modified)\n- **codeql.yml** - Also runs on schedule (weekly) in addition to being called by orchestrator\n\n## E2E Test Workflows\n\nIndividual E2E test workflows are called by `ci-e2e-all.yml`:\n- ci-e2e-badger.yaml\n- ci-e2e-cassandra.yml\n- ci-e2e-clickhouse.yml\n- ci-e2e-elasticsearch.yml\n- ci-e2e-grpc.yml\n- ci-e2e-kafka.yml\n- ci-e2e-memory.yaml\n- ci-e2e-opensearch.yml\n- ci-e2e-query.yml\n\nThese workflows use `workflow_call` only and don't have independent triggers.\n\n## Branch Protection\n\nTo require CI checks before merging, configure branch protection to require:\n- **CI Orchestrator / ci-success** - This single check represents the entire CI pipeline\n\nThis is much simpler than requiring 10+ individual workflow checks.\n\n## Local Development\n\nIndividual workflows can still be triggered manually via the GitHub Actions UI for testing or debugging purposes. However, on PR events, only the orchestrator runs to avoid duplicate work.\n\n## Benefits\n\n1. **Reduced Feedback Loop**: Trusted contributors get ~10m feedback instead of ~30m\n2. **Fail-Fast for External Contributors**: Expensive checks only run after cheaper ones pass, saving resources\n3. **Simplified Branch Protection**: Single `ci-success` check represents the entire CI pipeline\n4. **Centralized Concurrency Control**: Single kill-switch via `cancel-in-progress: true`\n5. **DRY Stage Workflows**: Both execution paths reuse the same `ci-orchestrator-stage*.yml` workflows\n6. **Maintainability**: Individual child workflows remain decoupled and independently testable\n"
  },
  {
    "path": ".github/workflows/ci-build-binaries.yml",
    "content": "name: Build binaries\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:\n  contents: read\n\njobs:\n  generate-matrix:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n    - name: define matrix\n      id: set-matrix\n      run: |\n        echo \"matrix=$(bash scripts/utils/platforms-to-gh-matrix.sh)\" >> $GITHUB_OUTPUT\n\n  build-binaries:\n    needs: generate-matrix\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}}\n    name: build-binaries-${{ matrix.os }}-${{ matrix.arch }}\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Fetch git tags\n      run: |\n        git fetch --prune --unshallow --tags\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Setup Node.js version\n      uses: ./.github/actions/setup-node.js\n\n    - name: Install tools\n      run: make install-ci\n\n    - name: Build platform binaries\n      run: make build-binaries-${{ matrix.os }}-${{ matrix.arch }}\n      env:\n        # Skip debug binaries on PRs to save CI time (4+ min per arch)\n        # Debug binaries are still built on main branch and during releases\n        SKIP_DEBUG_BINARIES: ${{ github.event_name == 'pull_request' && '1' || '0' }}\n"
  },
  {
    "path": ".github/workflows/ci-deploy-demo.yml",
    "content": "name: Deploy Jaeger Demo to OKE\n\non:\n  schedule:\n    - cron: '0 13 * * *'  # Daily at 8:00 AM US Eastern Time (ET)\n  workflow_dispatch:\n\npermissions: read-all\n\njobs:\n  deploy:\n    name: Deploy Jaeger to OKE Cluster\n    runs-on: ubuntu-latest\n    env:\n      OCI_CLI_USER: ${{ secrets.OCI_CLI_USER }}\n      OCI_CLI_TENANCY: ${{ secrets.OCI_CLI_TENANCY }}\n      OCI_CLI_FINGERPRINT: ${{ secrets.OCI_CLI_FINGERPRINT }}\n      OCI_CLI_KEY_CONTENT: ${{ secrets.OCI_CLI_KEY_CONTENT }}\n      OCI_CLI_REGION: ${{ secrets.OCI_CLI_REGION }}\n\n    steps:\n    - name: Configure kubectl with OKE\n      uses: oracle-actions/configure-kubectl-oke@77a733d79446dabe7bf0e58eb56197d33ce4dc58 # v1.5.0\n      with:\n        cluster: ${{ secrets.OKE_CLUSTER_OCID }}\n        enablePrivateEndpoint: false\n    \n    - name: Install Helm\n      uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4\n        \n    - name: Checkout jaeger repository\n      uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6\n\n    - name: Deploy using appropriate script\n      run: |\n        if [ \"${{ github.event_name }}\" = \"schedule\" ]; then\n          echo \"🕒 Scheduled run - using deploy-all.sh (upgrade mode)\"\n          bash ./examples/oci/deploy-all.sh\n        else\n          echo \"🔄 Manual run - using deploy-all.sh with clean mode (uninstall/install)\"\n          bash ./examples/oci/deploy-all.sh clean\n        fi         \n\n    - name: Send detailed Slack notification on failure\n      if: failure()\n      uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 # v2.3.0\n      env:\n        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}\n        SLACK_CHANNEL: '#jaeger-operations'\n        SLACK_COLOR: danger\n        SLACK_USERNAME: 'Jaeger CI Bot'\n        SLACK_ICON_EMOJI: ':warning:'\n        SLACK_TITLE: '🚨 Jaeger OKE Deployment Failed'\n        SLACK_MESSAGE: |\n          *Repository:* ${{ github.repository }}\n          *Workflow:* ${{ github.workflow }}\n          *Run ID:* ${{ github.run_id }}\n          *Trigger:* ${{ github.event_name }}\n          *Actor:* ${{ github.actor }}\n          \n          <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|🔗 View Failed Run>\n        SLACK_FOOTER: 'Jaeger CI/CD Pipeline'\n        \n"
  },
  {
    "path": ".github/workflows/ci-docker-all-in-one.yml",
    "content": "name: Build all-in-one\n\non:\n  workflow_call:\n\npermissions:\n  contents: read\n  packages: read  # This allows the runner to pull from GHCR\n\njobs:\n  all-in-one:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30 # max + 3*std over the last 2600 runs\n\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Fetch git tags\n      run: git fetch --prune --unshallow --tags\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Log in to GHCR\n      uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3\n      with:\n        registry: ghcr.io\n        username: ${{ github.actor }}\n        password: ${{ secrets.GITHUB_TOKEN }}\n\n    - uses: ./.github/actions/setup-node.js\n\n    - uses: ./.github/actions/setup-branch\n\n    - run: make install-ci\n\n    - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0\n\n    - name: Define BUILD_FLAGS var if running on a Pull Request or Merge Queue\n      run: |\n        case ${GITHUB_EVENT_NAME} in\n          pull_request|merge_group)\n            echo \"BUILD_FLAGS=-l -p linux/$(go env GOARCH)\" >> ${GITHUB_ENV}\n            ;;\n          *)\n            echo \"BUILD_FLAGS=\" >> ${GITHUB_ENV}\n            ;;\n        esac\n\n    - name: Build, test, and publish all-in-one image\n      run: |\n        bash scripts/build/build-all-in-one-image.sh ${{ env.BUILD_FLAGS }} v2\n      env:\n        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci-docker-build.yml",
    "content": "name: Build docker images\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:\n  contents: read\n\njobs:\n  docker-images:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Fetch git tags\n      run: git fetch --prune --unshallow --tags\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - uses: ./.github/actions/setup-node.js\n\n    - uses: ./.github/actions/setup-branch\n\n    - run: make install-ci\n\n    - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0\n\n    - name: Build only linux/amd64 container images for a Pull Request\n      if: github.ref_name != 'main'\n      # -D disables images with debugger\n      run: bash scripts/build/build-upload-docker-images.sh -D -p linux/amd64\n\n    - name: Build and upload all container images\n      if: github.ref_name == 'main'\n      run: bash scripts/build/build-upload-docker-images.sh\n      env:\n        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci-docker-hotrod.yml",
    "content": "name: CIT Hotrod\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:\n  contents: read\n\njobs:\n  hotrod:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        runtime: [docker, k8s]\n          \n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Fetch git tags\n      run: |\n        git fetch --prune --unshallow --tags\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - uses: ./.github/actions/setup-node.js\n\n    - uses: ./.github/actions/setup-branch\n\n    - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0\n\n    - name: Define BUILD_FLAGS var if running on a Pull Request\n      run: |\n        case ${GITHUB_EVENT_NAME} in\n          pull_request)\n            echo \"BUILD_FLAGS=-l -p linux/amd64\" >> ${GITHUB_ENV}\n            ;;\n          *)\n            echo \"BUILD_FLAGS=\" >> ${GITHUB_ENV}\n            ;;\n        esac\n    - name: Install kubectl\n      if: matrix.runtime == 'k8s'\n      uses: azure/setup-kubectl@3e0aec4d80787158d308d7b364cb1b702e7feb7f # v4\n      with:\n        version: 'latest'\n  \n    - name: Create k8s Kind Cluster\n      if: matrix.runtime == 'k8s'\n      uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1\n\n    - name: Build, test, and publish hotrod image\n      run:  bash scripts/build/build-hotrod-image.sh ${{ env.BUILD_FLAGS }} -r ${{ matrix.runtime }}\n      env:\n        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci-e2e-all.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Fan-out workflow that invokes all individual E2E integration test workflows\n# for every supported storage backend and feature.\n\nname: E2E Tests\n\non:\n  workflow_call:\n\npermissions:\n  contents: read\n\njobs:\n  badger:\n    uses: ./.github/workflows/ci-e2e-badger.yaml\n\n  cassandra:\n    uses: ./.github/workflows/ci-e2e-cassandra.yml\n\n  elasticsearch:\n    uses: ./.github/workflows/ci-e2e-elasticsearch.yml\n\n  grpc:\n    uses: ./.github/workflows/ci-e2e-grpc.yml\n\n  kafka:\n    uses: ./.github/workflows/ci-e2e-kafka.yml\n\n  memory:\n    uses: ./.github/workflows/ci-e2e-memory.yaml\n\n  opensearch:\n    uses: ./.github/workflows/ci-e2e-opensearch.yml\n\n  query:\n    uses: ./.github/workflows/ci-e2e-query.yml\n\n  clickhouse:\n    uses: ./.github/workflows/ci-e2e-clickhouse.yml\n\n  spm:\n    uses: ./.github/workflows/ci-e2e-spm.yml\n\n  tailsampling:\n    uses: ./.github/workflows/ci-e2e-tailsampling.yml\n"
  },
  {
    "path": ".github/workflows/ci-e2e-badger.yaml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Integration tests for Badger (embedded key-value store) storage backend.\n# direct: classic tests at the storage API layer, directly instantiating the storage implementation.\n# e2e: multi-process E2E tests via the trace ingestion and query APIs.\n\nname: CIT Badger\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  badger:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        storage_test: [direct, e2e]\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run Badger storage integration tests\n      env:\n        # Short interval so metricsCopier fires within the test duration.\n        # Propagated to the Jaeger subprocess via PropagateEnvVars in badger_test.go.\n        BADGER_METRICS_UPDATE_INTERVAL: 1s\n      run: |\n        case ${{ matrix.storage_test }} in\n          direct)\n            make badger-storage-integration-test\n            ;;\n          e2e)\n            STORAGE=badger make jaeger-v2-storage-integration-test\n            ;;\n        esac\n\n    - uses: ./.github/actions/verify-metrics-snapshot\n      if: matrix.storage_test == 'e2e'\n      with:\n        snapshot: metrics_snapshot_badger\n        artifact_key: metrics_snapshot_badger_${{ matrix.storage_test }}\n  \n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: badger_${{ matrix.storage_test }}\n"
  },
  {
    "path": ".github/workflows/ci-e2e-cassandra.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Integration tests for Cassandra storage backend, covering multiple Cassandra\n# major versions, schema versions, and both manual and auto schema creation.\n# direct: classic tests at the storage API layer, directly instantiating the storage implementation.\n# e2e: multi-process E2E tests via the trace ingestion and query APIs.\n\nname: CIT Cassandra\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  cassandra:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        storage_test: [direct, e2e]\n        create-schema: [manual, auto]\n        version:\n        - distribution: cassandra\n          major: 4.x\n          schema: v004\n        - distribution: cassandra\n          major: 5.x\n          schema: v004\n        exclude:\n        # Exclude direct as creating schema on startup is only available in e2e mode\n        - storage_test: direct\n          create-schema: auto\n    name: ${{ matrix.version.distribution }}-${{ matrix.version.major }} ${{ matrix.storage_test }} schema=${{ matrix.create-schema }}\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run cassandra integration tests\n      id: test-execution\n      run: bash scripts/e2e/cassandra.sh ${{ matrix.version.major }} ${{ matrix.version.schema }} ${{ matrix.storage_test }}\n      env:\n        SKIP_APPLY_SCHEMA: ${{ matrix.create-schema == 'auto' && true || false }}\n     \n    - uses: ./.github/actions/verify-metrics-snapshot\n      if: matrix.storage_test == 'e2e'\n      with:\n        snapshot: metrics_snapshot_cassandra\n        artifact_key: metrics_snapshot_cassandras_${{ matrix.version.major }}_${{ matrix.version.schema }}_${{ matrix.storage_test }}_${{ matrix.create-schema }}\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: cassandra-${{ matrix.version.major }}-${{ matrix.storage_test }}-${{ matrix.create-schema }}\n    \n    \n"
  },
  {
    "path": ".github/workflows/ci-e2e-clickhouse.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# E2E integration tests for ClickHouse storage backend (Jaeger v2 only).\n\nname: CIT ClickHouse\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  clickhouse:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run ClickHouse integration tests\n      id: test-execution\n      run: bash scripts/e2e/clickhouse.sh\n     \n    - uses: ./.github/actions/verify-metrics-snapshot\n      with:\n        snapshot: metrics_snapshot_clickhouse\n        artifact_key: metrics_snapshot_clickhouse\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: clickhouse\n    \n    \n"
  },
  {
    "path": ".github/workflows/ci-e2e-elasticsearch.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Integration tests for Elasticsearch storage backend, covering major versions 6.x through 9.x.\n# direct: classic tests at the storage API layer, directly instantiating the storage implementation.\n# e2e: multi-process E2E tests via the trace ingestion and query APIs.\n\nname: CIT Elasticsearch\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  elasticsearch:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        version:\n        - major: 6.x\n          distribution: elasticsearch\n          storage_test: direct\n        - major: 7.x\n          distribution: elasticsearch\n          storage_test: direct\n        - major: 8.x\n          distribution: elasticsearch\n          storage_test: direct\n        - major: 8.x\n          distribution: elasticsearch\n          storage_test: e2e\n        - major: 9.x\n          distribution: elasticsearch\n          storage_test: e2e\n    name: ${{ matrix.version.distribution }} ${{ matrix.version.major }} ${{ matrix.version.storage_test }}\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n        \n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: time settings\n      run: |\n        date\n        echo TZ=\"$TZ\"\n\n    - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0\n    - name: Run ${{ matrix.version.distribution }} integration tests\n      id: test-execution\n      run: bash scripts/e2e/elasticsearch.sh ${{ matrix.version.distribution }} ${{ matrix.version.major }} ${{ matrix.version.storage_test }}\n      \n    - uses: ./.github/actions/verify-metrics-snapshot\n      if: matrix.version.storage_test == 'e2e'\n      with:\n        snapshot: metrics_snapshot_elasticsearch\n        artifact_key: metrics_snapshot_elasticsearch_${{ matrix.version.major }}_${{ matrix.version.storage_test }}\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out,cover-index-cleaner.out,cover-index-rollover.out\n        flag: ${{ matrix.version.distribution }}-${{ matrix.version.major }}-${{ matrix.version.storage_test }}\n\n"
  },
  {
    "path": ".github/workflows/ci-e2e-grpc.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Integration tests for the gRPC remote storage plugin interface.\n# direct: classic tests at the storage API layer, directly instantiating the storage implementation.\n# e2e: multi-process E2E tests via the trace ingestion and query APIs.\n\nname: CIT gRPC\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  grpc:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        storage_test: [direct, e2e]\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run gRPC storage integration tests\n      run: |\n        case ${{ matrix.storage_test }} in\n          direct)\n            SPAN_STORAGE_TYPE=memory make grpc-storage-integration-test\n            ;;\n          e2e)\n            STORAGE=grpc make jaeger-v2-storage-integration-test\n            ;;\n        esac\n\n    - uses: ./.github/actions/verify-metrics-snapshot\n      if: matrix.storage_test == 'e2e'\n      with:\n        snapshot: metrics_snapshot_grpc\n        artifact_key: metrics_snapshot_grpc_${{ matrix.storage_test }}\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: grpc_${{ matrix.storage_test }}\n"
  },
  {
    "path": ".github/workflows/ci-e2e-kafka.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# E2E integration tests for Kafka as a span ingestion buffer (Jaeger v2),\n# verifying the Collector → Kafka → Ingester pipeline.\n\nname: CIT Kafka\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  kafka:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        kafka-version: [\"3.x\"]\n    name: kafka ${{ matrix.kafka-version }} v2\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run kafka integration tests\n      id: test-execution\n      run: bash scripts/e2e/kafka.sh -v ${{ matrix.kafka-version }}\n        \n    - uses: ./.github/actions/verify-metrics-snapshot\n      with:\n        snapshot: metrics_snapshot_kafka\n        artifact_key: metrics_snapshot_kafka_v2\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: kafka-${{ matrix.kafka-version }}-v2\n"
  },
  {
    "path": ".github/workflows/ci-e2e-memory.yaml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# E2E integration tests for the in-memory storage backend (Jaeger v2).\n\nname: CIT Memory\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  memory-v2:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n        \n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run Memory storage integration tests\n      run: |\n        STORAGE=memory_v2 make jaeger-v2-storage-integration-test\n        \n    - uses: ./.github/actions/verify-metrics-snapshot\n      with:\n        snapshot: metrics_snapshot_memory\n        artifact_key: metrics_snapshot_memory\n          \n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: memory_v2\n"
  },
  {
    "path": ".github/workflows/ci-e2e-opensearch.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Integration tests for OpenSearch storage backend, covering major versions 1.x through 3.x.\n# direct: classic tests at the storage API layer, directly instantiating the storage implementation.\n# e2e: multi-process E2E tests via the trace ingestion and query APIs.\n\nname: CIT OpenSearch\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  opensearch:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        version:\n        - major: 1.x\n          distribution: opensearch\n          storage_test: direct\n        - major: 2.x\n          distribution: opensearch\n          storage_test: direct\n        - major: 2.x\n          distribution: opensearch\n          storage_test: e2e\n        - major: 3.x\n          distribution: opensearch\n          storage_test: e2e\n    name: ${{ matrix.version.distribution }} ${{ matrix.version.major }} ${{ matrix.version.storage_test }}\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0\n\n    - name: Run ${{ matrix.version.distribution }} integration tests\n      id: test-execution\n      run: bash scripts/e2e/elasticsearch.sh ${{ matrix.version.distribution }} ${{ matrix.version.major }} ${{ matrix.version.storage_test }}\n\n    - uses: ./.github/actions/verify-metrics-snapshot\n      if: matrix.version.storage_test == 'e2e'\n      with:\n        snapshot: metrics_snapshot_opensearch\n        artifact_key: metrics_snapshot_opensearch_${{ matrix.version.major }}\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out,cover-index-cleaner.out,cover-index-rollover.out\n        flag: ${{ matrix.version.distribution }}-${{ matrix.version.major }}-${{ matrix.version.storage_test }}\n"
  },
  {
    "path": ".github/workflows/ci-e2e-query.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# E2E integration tests for a two-process deployment topology:\n#   - collector (config-remote-storage-backend.yaml): receives OTLP traces,\n#     writes to in-memory storage, exposes it via the remote_storage gRPC extension.\n#   - query (config-query.yaml): no ingestion pipeline, serves the query API\n#     by reading from the collector's remote_storage gRPC endpoint.\n\nname: CIT Query\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  query:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n        \n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run Memory storage integration tests\n      run: |\n        STORAGE=query make jaeger-v2-storage-integration-test\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: query\n"
  },
  {
    "path": ".github/workflows/ci-e2e-spm.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# E2E integration tests for Service Performance Monitoring (SPM), verifying\n# that span metrics are correctly derived and served via the metrics API,\n# with Prometheus, Elasticsearch, and OpenSearch as the metrics store.\n\nname: Test SPM\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:\n  contents: read\n\njobs:\n  spm:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        mode:\n        - name: v2\n          metricstore: prometheus\n        - name: v2 with ES\n          metricstore: elasticsearch\n        - name: v2 with OS\n          metricstore: opensearch\n\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Fetch git tags\n      run: |\n        git fetch --prune --unshallow --tags\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Setup Node.js version\n      uses: ./.github/actions/setup-node.js\n      \n    - name: Run SPM Test\n      run:  bash scripts/e2e/spm.sh -m ${{ matrix.mode.metricstore }}\n\n"
  },
  {
    "path": ".github/workflows/ci-e2e-tailsampling.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# E2E integration tests for the tail-based sampling processor, verifying\n# that sampling decisions are applied correctly after collecting full traces.\n\nname: Test Tail Sampling Processor\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  tailsampling-processor:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Run Tail Sampling Processor Integration Test\n      run: |\n        make tail-sampling-integration-test\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: tailsampling-processor\n"
  },
  {
    "path": ".github/workflows/ci-lint-checks.yaml",
    "content": "name: Lint Checks\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after a couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Print Jaeger version for no reason\n      run: make echo-version\n\n    - run: make install-test-tools\n\n    - run: make lint\n\n  pull-request-preconditions:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after a couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: ./.github/actions/block-pr-from-main-branch\n\n    - run: |\n        git fetch origin main\n        make lint-nocommit\n\n  dco-check:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after a couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - name: Set up Python 3.x for DCO check\n      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0\n      with:\n        python-version: '3.x'\n\n    - name: Run DCO check\n      if: ${{ github.event.pull_request.user.login != 'dependabot' && github.event_name != 'merge_group' }}\n      run: python3 scripts/lint/dco_check.py -b main -v --exclude-pattern '@users\\.noreply\\.github\\.com'\n      env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  idl-version-check:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: recursive\n        fetch-tags: true\n\n    - name: check jaeger-idl versions across git submodule and go.mod dependency\n      run: make lint-jaeger-idl-versions\n\n  generated-files-check:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: recursive\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Verify Protobuf types are up to date\n      run: make proto && { if git status --porcelain | grep '??'; then exit 1; else git diff --name-status --exit-code; fi }\n\n    - name: Verify Mockery types are up to date\n      run: make generate-mocks && { if git status --porcelain | grep '??'; then exit 1; else git diff --name-status --exit-code; fi }\n\n  lint-shell-scripts:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - run: sudo apt-get install shellcheck\n\n    - run: shellcheck scripts/**/*.sh\n\n    - name: Install shunit2 for shell unit tests\n      uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        repository: kward/shunit2\n        path: .tools/shunit2\n\n    - name: Run unit tests for scripts\n      run: |\n        SHUNIT2=.tools/shunit2 bash scripts/utils/run-tests.sh\n\n  binary-size-check:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Setup Node.js version\n      uses: ./.github/actions/setup-node.js\n\n    - name: Build jaeger binary\n      run: make build-jaeger\n\n    - name: Calculate jaeger binary size\n      run: |\n        TOTAL_SIZE=$(du -sb ./cmd/jaeger/jaeger-linux-amd64 | cut -f1)\n        echo \"$TOTAL_SIZE\" > ./new_jaeger_binary_size.txt\n        echo \"Total binary size: $TOTAL_SIZE bytes\"\n\n    - name: Restore previous binary size\n      id: cache-binary-size\n      uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb      # v5.0.1\n      with:\n        path: ./jaeger_binary_size.txt\n        key: jaeger_binary_size\n        restore-keys: |\n          jaeger_binary_size\n\n    - name: Compare `jaeger` binary sizes\n      if: ${{ (steps.cache-binary-size.outputs.cache-matched-key != '') && ((github.event_name != 'push') || (github.ref != 'refs/heads/main')) }}\n      run: |\n        set -euf -o pipefail\n        OLD_BINARY_SIZE=$(cat ./jaeger_binary_size.txt)\n        NEW_BINARY_SIZE=$(cat ./new_jaeger_binary_size.txt)\n        printf \"Previous binary size: %'d bytes\\n\" $OLD_BINARY_SIZE\n        printf \"New binary size:      %'d bytes\\n\" $NEW_BINARY_SIZE\n\n        PERCENTAGE_CHANGE=$(echo \"scale=2; ($NEW_BINARY_SIZE - $OLD_BINARY_SIZE) * 100 / $OLD_BINARY_SIZE\" | bc)\n        if (( $(echo \"$PERCENTAGE_CHANGE > 2.0\" | bc) == 1 )); then\n          echo \"❌ binary size increased by more than 2% ($PERCENTAGE_CHANGE%)\"\n          exit 1\n        else\n          echo \"✅ binary size change is within acceptable range ($PERCENTAGE_CHANGE%)\"\n        fi\n\n\n    - name: Remove previous *_binary_*.txt\n      run: |\n        rm -rf ./jaeger_binary_size.txt\n        mv ./new_jaeger_binary_size.txt ./jaeger_binary_size.txt\n\n    - name: Save new jaeger binary size\n      if: ${{ (github.event_name == 'push') && (github.ref == 'refs/heads/main') }}\n      uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb     # v5.0.1\n      with:\n        path: ./jaeger_binary_size.txt\n        key: jaeger_binary_size_${{ github.run_id }}\n\n  validate-renovate-config:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: false\n\n    - name: validate renovate config\n      run: |\n        docker run \\\n          -v $PWD/renovate.json:/usr/src/app/renovate.json \\\n          ghcr.io/renovatebot/renovate:latest \\\n          renovate-config-validator\n"
  },
  {
    "path": ".github/workflows/ci-orchestrator-stage1.yml",
    "content": "name: \"CI Orchestrator: Stage 1 (Linters)\"\n\non:\n  workflow_call:\n\njobs:\n  lint-checks:\n    uses: ./.github/workflows/ci-lint-checks.yaml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/ci-orchestrator-stage2.yml",
    "content": "name: \"CI Orchestrator: Stage 2 (Unit Tests)\"\n\non:\n  workflow_call:\n\njobs:\n  unit-tests:\n    uses: ./.github/workflows/ci-unit-tests.yml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/ci-orchestrator-stage3.yml",
    "content": "name: \"CI Orchestrator: Stage 3 (Docker, E2E, Binaries, Static Analysis)\"\n\non:\n  workflow_call:\n\njobs:\n  build-binaries:\n    uses: ./.github/workflows/ci-build-binaries.yml\n    secrets: inherit\n\n  docker-build:\n    uses: ./.github/workflows/ci-docker-build.yml\n    secrets: inherit\n\n  docker-all-in-one:\n    uses: ./.github/workflows/ci-docker-all-in-one.yml\n    secrets: inherit\n\n  docker-hotrod:\n    uses: ./.github/workflows/ci-docker-hotrod.yml\n    secrets: inherit\n\n  e2e-tests:\n    uses: ./.github/workflows/ci-e2e-all.yml\n    secrets: inherit\n\n  codeql:\n    uses: ./.github/workflows/codeql.yml\n    secrets: inherit\n\n  dependency-review:\n    uses: ./.github/workflows/dependency-review.yml\n    secrets: inherit\n\n  fossa:\n    uses: ./.github/workflows/fossa.yml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/ci-orchestrator.yml",
    "content": "name: CI Orchestrator\n\non:\n  pull_request:\n    branches: [main]\n  push:\n    branches: [main]\n  merge_group:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\n# Grant all permissions to allow child workflows to request what they need\n# Child workflows can downgrade permissions as needed (principle of least privilege)\npermissions: write-all\n\njobs:\n  # ============================================================================\n  # SETUP: Determine execution mode (sequential vs parallel)\n  # Parallel mode is used for trusted actors to reduce feedback loop from ~30m to ~10m.\n  # ============================================================================\n  setup:\n    runs-on: ubuntu-latest\n    outputs:\n      parallel: ${{ steps.mode.outputs.parallel }}\n    steps:\n      - name: Determine execution mode\n        id: mode\n        run: |\n          PARALLEL=false\n\n          # Parallel for push to main\n          if [[ \"${{ github.event_name }}\" == \"push\" && \"${{ github.ref }}\" == \"refs/heads/main\" ]]; then\n            echo \"Parallel: push to main\"\n            PARALLEL=true\n          else\n            echo \"Not triggered by push to main (event=${{ github.event_name }}, ref=${{ github.ref }})\"\n          fi\n\n          # Parallel for merge queue\n          if [[ \"${{ github.event_name }}\" == \"merge_group\" ]]; then\n            echo \"Parallel: merge_group event\"\n            PARALLEL=true\n          else\n            echo \"Not a merge_group event (event=${{ github.event_name }})\"\n          fi\n\n          # PR-specific checks (org membership, labels, and PR author login are only meaningful on pull_request events)\n          if [[ \"${{ github.event_name }}\" == \"pull_request\" ]]; then\n            # Parallel for org members.\n            # Use a live API call because author_association from the event payload is\n            # unreliable — it reports CONTRIBUTOR for org members who don't have direct\n            # repo access via a team.  Fall back to author_association when the API call\n            # fails (e.g. insufficient token permissions for fork PRs).\n            PR_AUTHOR=\"${{ github.event.pull_request.user.login }}\"\n            AUTHOR_ASSOC=\"${{ github.event.pull_request.author_association }}\"\n            if gh api --silent \"orgs/jaegertracing/members/$PR_AUTHOR\" 2>/dev/null; then\n              echo \"Parallel: org member ($PR_AUTHOR, verified via API)\"\n              PARALLEL=true\n            elif [[ \"$AUTHOR_ASSOC\" == \"MEMBER\" || \"$AUTHOR_ASSOC\" == \"OWNER\" ]]; then\n              echo \"Parallel: org member ($PR_AUTHOR, author_association=$AUTHOR_ASSOC)\"\n              PARALLEL=true\n            else\n              echo \"Not an org member ($PR_AUTHOR, author_association=$AUTHOR_ASSOC)\"\n            fi\n\n            # Parallel for known bots (dependency update automation)\n            if [[ \"$PR_AUTHOR\" == \"dependabot[bot]\" || \"$PR_AUTHOR\" == \"renovate-bot\" ]]; then\n              echo \"Parallel: bot PR author ($PR_AUTHOR)\"\n              PARALLEL=true\n            else\n              echo \"Not a known bot (PR author=$PR_AUTHOR)\"\n            fi\n\n            # Parallel if the ci:parallel label is applied to the PR.\n            # NOTE: re-running jobs does not refresh the event payload; a new run is needed\n            # to pick up labels added after the workflow was first triggered.\n            PR_LABELS=\"${{ join(github.event.pull_request.labels.*.name, ', ') }}\"\n            echo \"PR labels: ${PR_LABELS:-<none>}\"\n            if [[ \"${{ contains(github.event.pull_request.labels.*.name, 'ci:parallel') }}\" == \"true\" ]]; then\n              echo \"Parallel: ci:parallel label found\"\n              PARALLEL=true\n            else\n              echo \"ci:parallel label not found in: ${PR_LABELS:-<none>}\"\n            fi\n          else\n            echo \"Not a pull_request event — skipping PR-specific checks\"\n          fi\n\n          echo \"parallel=$PARALLEL\" >> \"$GITHUB_OUTPUT\"\n          echo \"Execution mode: parallel=$PARALLEL\"\n\n  # ============================================================================\n  # SCRIPTS UNIT TESTS: Fast, independent job for .github/scripts/ Jest suite.\n  # ============================================================================\n  ci-scripts:\n    name: CI Scripts Unit Tests\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0\n        with:\n          node-version: '24'\n          cache: 'npm'\n          cache-dependency-path: .github/scripts/package-lock.json\n      - name: Install Node dependencies (retry on transient registry failures)\n        working-directory: .github/scripts\n        run: |\n          set -euo pipefail\n\n          npm config set fetch-retries 5\n          npm config set fetch-retry-mintimeout 20000\n          npm config set fetch-retry-maxtimeout 120000\n\n          attempts=3\n          for i in $(seq 1 \"$attempts\"); do\n            echo \"npm ci attempt $i/$attempts\"\n            if npm ci; then\n              exit 0\n            fi\n\n            if [ \"$i\" -lt \"$attempts\" ]; then\n              sleep_time=$((i * 15))\n              echo \"npm ci failed, retrying in ${sleep_time}s\"\n              sleep \"$sleep_time\"\n            fi\n          done\n\n          echo \"npm ci failed after $attempts attempts\"\n          exit 1\n      - run: npm test\n        working-directory: .github/scripts\n\n  # ============================================================================\n  # SEQUENTIAL PATH (~30m): Default for external contributors.\n  # Stage 2 waits for Stage 1; Stage 3 waits for Stage 2.\n  # Active when parallel == false.\n  # ============================================================================\n  stage1-seq:\n    needs: [setup]\n    if: ${{ needs.setup.outputs.parallel == 'false' }}\n    uses: ./.github/workflows/ci-orchestrator-stage1.yml\n    secrets: inherit\n\n  stage2-seq:\n    needs: [setup, stage1-seq]\n    if: ${{ needs.setup.outputs.parallel == 'false' }}\n    uses: ./.github/workflows/ci-orchestrator-stage2.yml\n    secrets: inherit\n\n  stage3-seq:\n    needs: [setup, stage2-seq]\n    if: ${{ needs.setup.outputs.parallel == 'false' }}\n    uses: ./.github/workflows/ci-orchestrator-stage3.yml\n    secrets: inherit\n\n  # ============================================================================\n  # PARALLEL PATH (~10m): For trusted maintainers, merge queue, and main branch.\n  # All stages start simultaneously after setup.\n  # Active when parallel == true.\n  # ============================================================================\n  stage1-fast:\n    needs: [setup]\n    if: ${{ needs.setup.outputs.parallel == 'true' }}\n    uses: ./.github/workflows/ci-orchestrator-stage1.yml\n    secrets: inherit\n\n  stage2-fast:\n    needs: [setup]\n    if: ${{ needs.setup.outputs.parallel == 'true' }}\n    uses: ./.github/workflows/ci-orchestrator-stage2.yml\n    secrets: inherit\n\n  stage3-fast:\n    needs: [setup]\n    if: ${{ needs.setup.outputs.parallel == 'true' }}\n    uses: ./.github/workflows/ci-orchestrator-stage3.yml\n    secrets: inherit\n\n  # ============================================================================\n  # FINAL GATEKEEPER: Use this job for Branch Protection.\n  # Validates whichever execution path was taken (sequential or parallel).\n  # ============================================================================\n  ci-success:\n    name: All CI Checks Passed\n    runs-on: ubuntu-latest\n    if: always()\n    needs: [setup, ci-scripts, stage1-seq, stage2-seq, stage3-seq, stage1-fast, stage2-fast, stage3-fast]\n    steps:\n      - name: Check setup status\n        if: ${{ needs.setup.result != 'success' }}\n        run: |\n          echo \"❌ Setup job failed or was cancelled.\"\n          exit 1\n\n      - name: Check CI scripts tests\n        if: ${{ needs.ci-scripts.result != 'success' }}\n        run: |\n          echo \"❌ CI scripts unit tests failed or were cancelled.\"\n          exit 1\n\n      - name: Check sequential path\n        if: ${{ needs.setup.outputs.parallel == 'false' }}\n        run: |\n          S1=\"${{ needs.stage1-seq.result }}\"\n          S2=\"${{ needs.stage2-seq.result }}\"\n          S3=\"${{ needs.stage3-seq.result }}\"\n          if [[ \"$S1\" != \"success\" || \"$S2\" != \"success\" || \"$S3\" != \"success\" ]]; then\n            echo \"❌ CI failed on sequential path. Stage 1: $S1, Stage 2: $S2, Stage 3: $S3\"\n            exit 1\n          fi\n          echo \"✅ CI passed on sequential path.\"\n\n      - name: Check parallel path\n        if: ${{ needs.setup.outputs.parallel == 'true' }}\n        run: |\n          S1=\"${{ needs.stage1-fast.result }}\"\n          S2=\"${{ needs.stage2-fast.result }}\"\n          S3=\"${{ needs.stage3-fast.result }}\"\n          if [[ \"$S1\" != \"success\" || \"$S2\" != \"success\" || \"$S3\" != \"success\" ]]; then\n            echo \"❌ CI failed on parallel path. Stage 1: $S1, Stage 2: $S2, Stage 3: $S3\"\n            exit 1\n          fi\n          echo \"✅ CI passed on parallel path.\"\n\n      - name: Validate execution path was determined\n        run: |\n          PARALLEL=\"${{ needs.setup.outputs.parallel }}\"\n          if [[ \"$PARALLEL\" != \"true\" && \"$PARALLEL\" != \"false\" ]]; then\n            echo \"❌ Invalid parallel mode: '$PARALLEL' (expected 'true' or 'false')\"\n            exit 1\n          fi\n\n  # ============================================================================\n  # SUMMARY REPORT: Runs after all CI stages pass.\n  # Computes coverage gating and metrics comparison, uploads ci-summary artifact.\n  # Fails visibly in PR Checks if coverage drops or metrics regress.\n  # ci-summary-report-publish.yml (workflow_run) reads the artifact and posts\n  # the PR comment and check runs — even when this job fails.\n  # ============================================================================\n  summary:\n    name: CI Summary Report\n    needs: [ci-success]\n    if: always() && needs.ci-success.result == 'success'\n    uses: ./.github/workflows/ci-summary-report.yml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/ci-release.yml",
    "content": "name: Publish release\n\non:\n  release:\n    types:\n      - published\n\n  workflow_dispatch:\n    inputs:\n      dry_run:\n        required: true\n        type: boolean\n        description: Do a test run. It will only build one platform (for speed) and will not push artifacts.\n\n      overwrite:\n        required: true\n        type: boolean\n        description: Allow overwriting artifacts.\n\njobs:\n  publish-release:\n    permissions:\n      contents: write\n      deployments: write\n      packages: read  # This allows the runner to pull from GHCR\n    if: github.repository == 'jaegertracing/jaeger'\n    runs-on: jaeger-linux-amd64-32core-1200GB_SSD\n\n    steps:\n    - name: Clean up some disk space\n      # We had an issue where the workflow was running out of disk space,\n      # because it downloads so many Docker images for different platforms.\n      # Here we delete some stuff from the VM that we do not use.\n      # Inspired by https://github.com/jlumbroso/free-disk-space.\n      run: |\n        sudo rm -rf /usr/local/lib/android || true\n        df -h /\n\n    - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Fetch git tags\n      run: |\n        git fetch --prune --unshallow --tags\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - uses: ./.github/actions/setup-node.js\n\n    - name: Determine parameters\n      id: params\n      run: |\n        docker_flags=()\n        if [[ \"${{ inputs.dry_run }}\" == \"true\" ]]; then\n          docker_flags=(\"${docker_flags[@]}\" -l -p linux/amd64)\n          echo \"platforms=linux/amd64\" >> $GITHUB_OUTPUT\n          echo \"gpg_key_override=-k skip\" >> $GITHUB_OUTPUT\n        else\n          echo \"platforms=$(make echo-platforms)\" >> $GITHUB_OUTPUT\n        fi\n        if [[ \"${{ inputs.overwrite }}\" == \"true\" ]]; then\n          docker_flags=(\"${docker_flags[@]}\" -o)\n        fi\n        echo \"docker_flags=${docker_flags[@]}\" >> $GITHUB_OUTPUT\n        cat $GITHUB_OUTPUT\n\n    - name: Export BRANCH variable and validate it is a semver\n      # Many scripts depend on BRANCH variable. We do not want to\n      # use ./.github/actions/setup-branch here because it may set\n      # BRANCH=main when the workflow is triggered manually.\n      run: |\n        BRANCH=$(make echo-version)\n        echo Validate that the latest tag ${BRANCH} is in semver format\n        echo ${BRANCH} | grep -E '^v[0-9]+.[0-9]+.[0-9]+(-rc[0-9]+)?$'\n        echo \"BRANCH=${BRANCH}\" >> ${GITHUB_ENV}\n\n    - name: Configure GPG Key\n      if: ${{ inputs.dry_run != true }}\n      uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0\n      with:\n        gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}\n        passphrase: ${{ secrets.GPG_PASSPHRASE }}\n\n    - name: Build all binaries\n      run: make build-all-platforms PLATFORMS=${{ steps.params.outputs.platforms }}\n\n    - name: Package binaries\n      run: |\n        bash scripts/build/package-deploy.sh \\\n          -p ${{ steps.params.outputs.platforms }} \\\n          ${{ steps.params.outputs.gpg_key_override }}\n\n    - name: Upload binaries\n      if: ${{ inputs.dry_run != true }}\n      uses: svenstaro/upload-release-action@5e35e583720436a2cc5f9682b6f55657101c1ea1 # 2.11.1\n      with:\n        file: '{deploy/*.tar.gz,deploy/*.zip,deploy/*.sha256sum.txt,deploy/*.asc}'\n        file_glob: true\n        overwrite: ${{ inputs.overwrite }}\n        tag: ${{ env.BRANCH }}\n        repo_token: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Delete the release artifacts after uploading them.\n      run: |\n        rm -rf deploy || true\n        df -h /\n\n    - name: Log in to GHCR\n      uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3\n      with:\n        registry: ghcr.io\n        username: ${{ github.actor }}\n        password: ${{ secrets.GITHUB_TOKEN }}\n\n    - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0\n\n    - name: Build and upload all container images\n      # -B skips building the binaries since we already did that above\n      run: |\n        bash scripts/build/build-upload-docker-images.sh -B \\\n          ${{ steps.params.outputs.docker_flags }}\n      env:\n        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}\n\n    - name: Build, test, and publish jaeger image\n      run: |\n        bash scripts/build/build-all-in-one-image.sh \\\n          ${{ steps.params.outputs.docker_flags }}\n      env:\n        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}\n\n    - name: Build, test, and publish hotrod image\n      run: |\n        bash scripts/build/build-hotrod-image.sh \\\n          ${{ steps.params.outputs.docker_flags }}\n      env:\n        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}\n        QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}\n\n    - name: Generate SBOM\n      uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0.23.0\n      with:\n        output-file: jaeger-SBOM.spdx.json\n        upload-release-assets: false\n        upload-artifact: false\n\n    - name: Upload SBOM\n      # Upload SBOM manually, because anchore/sbom-action does not do that\n      # when the workflow is triggered manually, only from a release.\n      uses: svenstaro/upload-release-action@5e35e583720436a2cc5f9682b6f55657101c1ea1 # 2.11.1\n      if: ${{ inputs.dry_run != true }}\n      with:\n        file: jaeger-SBOM.spdx.json\n        overwrite: ${{ inputs.overwrite }}\n        tag: ${{ env.BRANCH }}\n        repo_token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci-summary-report-publish.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# CI Summary Report (Publish): posts PR comments and check runs from the\n# pre-computed ci-summary artifact produced by ci-summary-report.yml.\n#\n# Triggered by workflow_run on CI Orchestrator completion (success OR failure).\n# pull_request workflows from forks run with read-only permissions regardless of\n# what the workflow file declares, so they cannot post PR comments or check runs.\n# workflow_run always executes in the base repository context and gets the\n# permissions declared here — that is why posting is split into this workflow.\n# The heavy computation runs inside the CI Orchestrator itself (ci-summary-report.yml),\n# making gate failures immediately visible in the PR Checks table without waiting\n# for this workflow's separate approval step.\n#\n# Design: docs/adr/004-migrating-coverage-gating-to-github-actions.md\n# Security model: see .github/scripts/ci-summary-report-publish.js\n\nname: CI Summary Report (Publish)\non:\n  workflow_run:\n    workflows: [\"CI Orchestrator\"]\n    types: [completed]\n  workflow_dispatch:\n    inputs:\n      run_id:\n        description: 'CI Orchestrator run ID to publish summary for'\n        required: true\n      pr_number:\n        description: 'PR number to post summary report for'\n        required: true\npermissions:\n  contents: read\n  pull-requests: write\n  checks: write\n  actions: read\njobs:\n  publish:\n    name: Post Summary\n    if: |\n      github.event_name == 'workflow_dispatch' ||\n      github.event.workflow_run.conclusion == 'success' ||\n      github.event.workflow_run.conclusion == 'failure'\n    runs-on: ubuntu-latest\n    steps:\n      # Resolve the CI Orchestrator run ID and head SHA.\n      # PR number is read from the ci-summary artifact after download (see below).\n      - name: Resolve source run\n        id: source-run\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          if [ \"${{ github.event_name }}\" == \"workflow_dispatch\" ]; then\n            RUN_ID=\"${{ github.event.inputs.run_id }}\"\n            PR_NUMBER=\"${{ github.event.inputs.pr_number }}\"\n            for var in RUN_ID PR_NUMBER; do\n              if ! [[ \"${!var}\" =~ ^[1-9][0-9]*$ ]]; then\n                echo \"::error::Invalid $var input: must be a positive integer, got: '${!var}'\"\n                exit 1\n              fi\n            done\n            echo \"pr_number=$PR_NUMBER\" >> $GITHUB_OUTPUT\n            HEAD_SHA=$(gh api \"repos/${{ github.repository }}/actions/runs/$RUN_ID\" --jq '.head_sha')\n          else\n            RUN_ID=\"${{ github.event.workflow_run.id }}\"\n            HEAD_SHA=\"${{ github.event.workflow_run.head_sha }}\"\n          fi\n\n          echo \"run_id=$RUN_ID\" >> $GITHUB_OUTPUT\n          echo \"head_sha=$HEAD_SHA\" >> $GITHUB_OUTPUT\n          echo \"source_run_url=https://github.com/${{ github.repository }}/actions/runs/$RUN_ID\" >> $GITHUB_OUTPUT\n          echo \"summary_run_url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\" >> $GITHUB_OUTPUT\n\n      # Checkout the base repo so that .github/scripts/ci-summary-report-publish.js\n      # is available for require() in the github-script step below.\n      # Do NOT checkout the PR head: this workflow must only run base-repo code.\n      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n      # Download only the ci-summary artifact (pre-computed by ci-summary-report.yml).\n      - name: Download CI summary artifact\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          gh run download \"${{ steps.source-run.outputs.run_id }}\" \\\n            --repo \"${{ github.repository }}\" \\\n            --name ci-summary \\\n            --dir .artifacts \\\n          || echo \"::warning::ci-summary artifact not found; proceeding without summary artifact (later checks may fail).\"\n\n      # For workflow_run triggers, read pr_number from ci-summary.json (written\n      # by ci-summary-report.yml inside the CI Orchestrator, where\n      # github.event.pull_request.number is accurate).\n      # For workflow_dispatch, pr_number is already set from the input above.\n      - name: Read PR number from artifact\n        id: pr\n        if: github.event_name != 'workflow_dispatch'\n        run: |\n          if [ -f .artifacts/ci-summary.json ]; then\n            PR_NUMBER=$(jq -r '.pr_number // empty' .artifacts/ci-summary.json 2>/dev/null || true)\n            if [ -n \"$PR_NUMBER\" ]; then\n              echo \"Found PR #$PR_NUMBER in ci-summary.json\"\n              echo \"pr_number=$PR_NUMBER\" >> $GITHUB_OUTPUT\n            else\n              echo \"No PR number in ci-summary.json; PR comment will be skipped.\"\n            fi\n          else\n            echo \"ci-summary.json not found; PR comment will be skipped.\"\n          fi\n\n      - name: Post PR comment and create check runs\n        if: steps.source-run.outputs.head_sha\n        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          script: |\n            const handler = require('./.github/scripts/ci-summary-report-publish.js');\n            await handler({\n              github, core, fs: require('fs'),\n              inputs: {\n                owner:      context.repo.owner,\n                repo:       context.repo.repo,\n                headSha:    '${{ steps.source-run.outputs.head_sha }}',\n                prNumber:   '${{ steps.source-run.outputs.pr_number || steps.pr.outputs.pr_number }}',\n                ciRunUrl:   '${{ steps.source-run.outputs.source_run_url }}',\n                publishUrl: '${{ steps.source-run.outputs.summary_run_url }}',\n              },\n            });\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci-summary-report.yml",
    "content": "# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# CI Summary Report: reusable workflow (workflow_call) invoked by CI Orchestrator.\n# Computes metrics comparison and coverage gating, then uploads a ci-summary\n# artifact with the results. ci-summary-report-publish.yml (triggered by\n# workflow_run) reads that artifact to post PR comments and check runs, because\n# pull_request workflows from forks cannot write to the upstream repository.\n#\n# Design: docs/adr/004-migrating-coverage-gating-to-github-actions.md\n\nname: CI Summary Report\non:\n  workflow_call:\npermissions:\n  contents: read\n  actions: write  # required for actions/cache save and gh run download\n\njobs:\n  summary-report:\n    name: Summary Report\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6\n        with:\n          ref: ${{ github.sha }}\n\n      # Download all artifacts uploaded by the calling (CI Orchestrator) run.\n      # This includes coverage-* and metrics_snapshot_* artifacts from all CI jobs.\n      - name: Download all artifacts\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          gh run download \"${{ github.run_id }}\" \\\n            --repo \"${{ github.repository }}\" --dir .artifacts\n\n      - name: Install dependencies\n        run: python3 -m pip install prometheus-client\n\n      - name: Compare metrics and generate summary\n        id: compare-metrics\n        shell: bash\n        run: bash ./scripts/e2e/metrics_summary.sh\n\n      - name: Set up Go for coverage tools\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n          cache-dependency-path: |\n            ./go.sum\n            ./internal/tools/go.sum\n\n      - name: Install coverage tools\n        run: make install-coverage-tools\n\n      - name: Merge coverage profiles\n        id: merge-coverage\n        run: |\n          mapfile -t COVER_FILES < <(find .artifacts -path \"*/coverage-*/*.out\" -type f)\n          if [ ${#COVER_FILES[@]} -eq 0 ]; then\n            echo \"No coverage files found; skipping coverage gate.\"\n            echo \"skipped=true\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"Merging ${#COVER_FILES[@]} coverage profiles\"\n            ./.tools/gocovmerge \"${COVER_FILES[@]}\" > .artifacts/merged-coverage.out\n            echo \"skipped=false\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: Filter excluded paths from merged coverage\n        if: success() && steps.merge-coverage.outputs.skipped == 'false'\n        run: |\n          # Applies the same exclusions as .codecov.yml (single source of truth).\n          # filter_coverage.py modifies the file in-place.\n          python3 scripts/e2e/filter_coverage.py .artifacts/merged-coverage.out\n          echo \"Coverage lines after filtering: $(wc -l < .artifacts/merged-coverage.out)\"\n\n      - name: Calculate current coverage percentage\n        if: success() && steps.merge-coverage.outputs.skipped == 'false'\n        id: coverage\n        run: |\n          PCT=$(go tool cover -func=.artifacts/merged-coverage.out \\\n            | grep \"^total:\" | awk '{print $3}' | tr -d '%')\n          echo \"percentage=${PCT}\" >> \"$GITHUB_OUTPUT\"\n          echo \"${PCT}\" > .artifacts/current-coverage.txt\n          echo \"Current coverage: ${PCT}%\"\n\n      - name: Restore baseline coverage from main\n        if: success() && steps.merge-coverage.outputs.skipped == 'false'\n        id: restore-baseline\n        uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57\n        with:\n          path: .artifacts/baseline-coverage.txt\n          # Exact match intentionally never hits (run IDs differ between runs).\n          # The restore-keys prefix coverage-baseline_ always falls back to the\n          # most recently created cache entry (GitHub returns the newest match\n          # for prefix lookups), which is the latest passing main-branch run.\n          # The trailing underscore avoids matching a plain \"coverage-baseline\"\n          # key if one were ever created.\n          # Storage is negligible: each entry is a single number (~10 B) and\n          # GitHub automatically evicts entries unused for 7 days.\n          key: coverage-baseline_${{ github.run_id }}\n          restore-keys: |\n            coverage-baseline_\n\n      - name: Gate on coverage regression\n        if: success() && steps.merge-coverage.outputs.skipped == 'false'\n        id: coverage-gate\n        run: |\n          CURRENT=\"${{ steps.coverage.outputs.percentage }}\"\n          BASELINE_MSG=\"(no baseline yet)\"\n          failure_reasons=()\n\n          if [ -z \"$CURRENT\" ]; then\n            failure_reasons+=(\"coverage percentage is empty; go tool cover may have failed\")\n          else\n            # Gate 1: absolute minimum threshold\n            MINIMUM=95.0\n            if (( $(echo \"$CURRENT < $MINIMUM\" | bc -l) )); then\n              failure_reasons+=(\"coverage ${CURRENT}% is below minimum ${MINIMUM}%\")\n            fi\n\n            # Gate 2: no regression vs main baseline\n            if [ -f .artifacts/baseline-coverage.txt ]; then\n              BASELINE=$(cat .artifacts/baseline-coverage.txt)\n              if [ -z \"$BASELINE\" ]; then\n                failure_reasons+=(\"baseline coverage file is empty; cannot perform regression check\")\n              else\n                BASELINE_MSG=\"(baseline ${BASELINE}%)\"\n                if (( $(echo \"$CURRENT < $BASELINE\" | bc -l) )); then\n                  failure_reasons+=(\"coverage dropped from ${BASELINE}% to ${CURRENT}%\")\n                fi\n              fi\n            fi\n          fi\n\n          if [ ${#failure_reasons[@]} -gt 0 ]; then\n            msg=$(IFS='; '; echo \"${failure_reasons[*]}\")\n            echo \"conclusion=failure\" >> \"$GITHUB_OUTPUT\"\n            echo \"summary=${msg}\" >> \"$GITHUB_OUTPUT\"\n            echo \"::error::${msg}\"\n          else\n            echo \"conclusion=success\" >> \"$GITHUB_OUTPUT\"\n            echo \"summary=Coverage ${CURRENT}% ${BASELINE_MSG}\" >> \"$GITHUB_OUTPUT\"\n            echo \"Coverage ${CURRENT}% ${BASELINE_MSG}: OK\"\n          fi\n\n      # Serialize only strongly-typed values to JSON so ci-summary-report-publish.yml\n      # never handles free-form text from test output (which could contain injections).\n      # All display text is constructed from this structured data by trusted publish-\n      # workflow code running in the base repository context.\n      #\n      # metrics_snapshots is an array of per-snapshot change data (metric names and\n      # counts) produced by metrics_summary.sh.  The publish workflow validates every\n      # field before rendering (see sanitizeSnapshots in ci-summary-report-publish.js).\n      - name: Save conclusions for publish workflow\n        if: always()\n        env:\n          PR_NUMBER:              ${{ github.event.pull_request.number }}\n          METRICS_CONCLUSION:     ${{ steps.compare-metrics.outputs.CONCLUSION }}\n          METRICS_TOTAL:          ${{ steps.compare-metrics.outputs.TOTAL_CHANGES }}\n          METRICS_INFRA_ERRORS:   ${{ steps.compare-metrics.outputs.INFRA_ERRORS }}\n          COVERAGE_MERGE_OUTCOME: ${{ steps.merge-coverage.outcome }}\n          COVERAGE_SKIPPED:       ${{ steps.merge-coverage.outputs.skipped }}\n          COVERAGE_CONCLUSION:    ${{ steps.coverage-gate.outputs.conclusion }}\n          COVERAGE_PCT:           ${{ steps.coverage.outputs.percentage }}\n        run: |\n          mkdir -p .artifacts\n          python3 - <<'PYEOF'\n          import json, os\n\n          metrics_conclusion = os.environ.get('METRICS_CONCLUSION') or 'failure'\n          metrics_total_env = os.environ.get('METRICS_TOTAL')\n          if metrics_total_env not in (None, ''):\n              metrics_total = int(metrics_total_env)\n          else:\n              metrics_total = None\n          has_infra_errors   = os.environ.get('METRICS_INFRA_ERRORS') == 'true'\n\n          coverage_merge_outcome = os.environ.get('COVERAGE_MERGE_OUTCOME', '')\n          coverage_skipped       = os.environ.get('COVERAGE_SKIPPED') == 'true'\n          if coverage_merge_outcome in ('failure', 'cancelled'):\n              # merge-coverage failed before writing its skipped output; treat as failure\n              # so a pipeline error is not silently reported as coverage skipped/success.\n              coverage_conclusion = 'failure'\n          elif coverage_skipped:\n              coverage_conclusion = 'skipped'\n          else:\n              coverage_conclusion = os.environ.get('COVERAGE_CONCLUSION') or 'skipped'\n\n          coverage_pct      = None\n          coverage_baseline = None\n          if not coverage_skipped:\n              try:\n                  coverage_pct = float(os.environ.get('COVERAGE_PCT') or '')\n              except (ValueError, TypeError):\n                  pass\n              try:\n                  with open('.artifacts/baseline-coverage.txt') as f:\n                      coverage_baseline = float(f.read().strip())\n              except (FileNotFoundError, ValueError):\n                  pass\n\n          # Load per-snapshot metric change data (produced by metrics_summary.sh).\n          # If the file is missing or malformed, set to None — the publish\n          # workflow treats null/missing as \"no detail available\".\n          metrics_snapshots = None\n          try:\n              with open('.artifacts/metrics_snapshots.json') as f:\n                  metrics_snapshots = json.load(f)\n          except (FileNotFoundError, json.JSONDecodeError, ValueError):\n              pass\n\n          pr_number_env = os.environ.get('PR_NUMBER')\n          pr_number = int(pr_number_env) if pr_number_env else None\n\n          data = {\n              'pr_number':               pr_number,\n              'metrics_conclusion':      metrics_conclusion,\n              'metrics_total_changes':   metrics_total,\n              'metrics_has_infra_errors': has_infra_errors,\n              'metrics_snapshots':       metrics_snapshots,\n              'coverage_conclusion':     coverage_conclusion,\n              'coverage_percentage':     coverage_pct,\n              'coverage_baseline':       coverage_baseline,\n              'coverage_skipped':        coverage_skipped,\n          }\n          with open('.artifacts/ci-summary.json', 'w') as f:\n              json.dump(data, f, indent=2)\n          print(json.dumps(data, indent=2))\n          PYEOF\n\n      - name: Upload CI summary artifact\n        if: always()\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7\n        with:\n          name: ci-summary\n          path: .artifacts/ci-summary.json\n          retention-days: 7\n          if-no-files-found: warn\n\n      # Save baseline coverage on main-branch runs so PRs can compare against it.\n      # Only save when the coverage gate passes so a failing/partial run never\n      # overwrites a valid baseline with a bad value.\n      - name: Save coverage baseline on main branch\n        if: >-\n          github.ref == 'refs/heads/main' &&\n          steps.merge-coverage.outputs.skipped == 'false' &&\n          steps.coverage-gate.outputs.conclusion == 'success'\n        run: cp .artifacts/current-coverage.txt .artifacts/baseline-coverage.txt\n\n      - name: Cache coverage baseline\n        if: >-\n          github.ref == 'refs/heads/main' &&\n          steps.merge-coverage.outputs.skipped == 'false' &&\n          steps.coverage-gate.outputs.conclusion == 'success'\n        uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57\n        with:\n          path: .artifacts/baseline-coverage.txt\n          key: coverage-baseline_${{ github.run_id }}\n\n      # Fail the job (and the calling CI Orchestrator run) so the coverage/metrics\n      # regression is immediately visible in the PR Checks table.\n      # The ci-summary artifact is already uploaded above (if: always()), so\n      # ci-summary-report-publish.yml can still post the PR comment and check runs.\n      - name: Fail if coverage or metrics gate failed\n        if: |\n          steps.compare-metrics.outputs.CONCLUSION == 'failure' ||\n          steps.coverage-gate.outputs.conclusion == 'failure'\n        run: |\n          echo \"Metrics: ${{ steps.compare-metrics.outputs.CONCLUSION }}\"\n          echo \"Coverage: ${{ steps.coverage-gate.outputs.conclusion }}\"\n          exit 1\n"
  },
  {
    "path": ".github/workflows/ci-unit-tests-go-tip.yml",
    "content": "name: Unit Tests on Go Tip\n\non:\n  push:\n    branches: [main]\n\n  workflow_dispatch:\n\n  # We normally don't want this workflow to run on PRs, only on main branch.\n  # Unless the workflow file itself or the setup action is modified.\n  pull_request:\n    branches: [main]\n    paths:\n      - '.github/workflows/ci-unit-tests-go-tip.yml'\n      - '.github/actions/setup-go-tip/**'\n\npermissions:\n  contents: read\n\njobs:\n  unit-tests-go-tip:\n    permissions:\n      checks: write\n    runs-on: ubuntu-latest\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - name: Install Go Tip\n      uses: ./.github/actions/setup-go-tip\n      with:\n        gh_token: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Clean Go cache for gotip\n      run: |\n        go clean -cache\n        go clean -modcache\n\n    - name: Run unit tests\n      run: make test-ci\n\n\n"
  },
  {
    "path": ".github/workflows/ci-unit-tests.yml",
    "content": "name: Unit Tests\n\non:\n  workflow_call:\n\npermissions:\n  contents: read\n\njobs:\n  unit-tests:\n    permissions:\n      checks: write\n    runs-on: ubuntu-latest\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n    - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n    - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n        cache-dependency-path: ./go.sum\n\n    # download dependencies separately to keep unit test step's output cleaner\n    - name: go mod download\n      run: go mod download\n\n    - name: Install test deps\n      # even though the same target runs from test-ci, running it separately makes for cleaner log in GH workflow\n      run: make install-test-tools\n\n    - name: Run unit tests\n      run: make test-ci\n\n    - name: Upload coverage to codecov\n      uses: ./.github/actions/upload-codecov\n      with:\n        files: cover.out\n        flag: unittests\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n  workflow_call:\n  schedule:\n    - cron: '31 6 * * 1'\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:  # added using https://github.com/step-security/secure-workflows\n  contents: read\n\njobs:\n  codeql-analyze:\n    # Skip merge_group to avoid duplicate runs (code already scanned on pull_request)\n    # See https://github.com/github/codeql-action/issues/1537\n    if: ${{ github.event_name != 'merge_group' }}\n    name: CodeQL Analyze\n    runs-on: ubuntu-latest\n\n    permissions:\n      security-events: write\n      actions: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go', 'python' ]\n\n    steps:\n    - name: Harden Runner\n      uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n      with:\n        egress-policy: audit\n\n    - name: Checkout repository\n      uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      with:\n        submodules: true\n\n    - name: Setup Go\n      if: matrix.language == 'go'\n      uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n      with:\n        go-version: 1.26.x\n\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0\n      with:\n        languages: ${{ matrix.language }}\n\n    # Use Autobuild for Python (it works fine for interpreted languages)\n    - name: Autobuild (Python)\n      if: matrix.language == 'python'\n      uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0\n\n    # Explicit build for Go - required for CodeQL to analyze compiled code\n    - name: Build Go code\n      if: matrix.language == 'go'\n      run: |\n        # Build all Go binaries to ensure CodeQL can analyze them\n        go build -v ./...\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0\n"
  },
  {
    "path": ".github/workflows/dco_merge_group.yml",
    "content": "# Fake \"DCO check\" workflow inspired by https://github.com/onnx/onnx/pull/5398/files.\n# The regular DCO check is required, but it does not run from a merge queue and there is\n# no way to configure it to run.\nname: DCO\non:\n  merge_group:\n\npermissions:  \n  contents: read\njobs:\n  DCO:\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo \"Fake DCO check to avoid blocking the merge queue\""
  },
  {
    "path": ".github/workflows/dependency-review.yml",
    "content": "# Dependency Review Action\n#\n# This Action will scan dependency manifest files that change as part of a Pull Request,\n# surfacing known-vulnerable versions of the packages declared or updated in the PR.\n# Once installed, if the workflow run is marked as required,\n# PRs introducing known-vulnerable packages will be blocked from merging.\n#\n# Source repository: https://github.com/actions/dependency-review-action\nname: 'Dependency Review'\non:\n  workflow_call:\n\npermissions:\n  contents: read\n\njobs:\n  dependency-review:\n    if: |\n      ${{ github.event_name == 'pull_request' ||\n      github.event_name == 'pull_request_target' ||\n      github.event_name == 'merge_group' }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n        with:\n          egress-policy: audit\n\n      - name: 'Checkout Repository'\n        uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n      - name: 'Dependency Review'\n        uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0\n"
  },
  {
    "path": ".github/workflows/fossa.yml",
    "content": "name: FOSSA\n\non:\n  workflow_call:\n\n# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions\npermissions:\n  contents: read\n\njobs:\n  fossa-license-scan:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n        with:\n          egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs\n\n      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: 1.26.x\n\n      - name: Add GOPATH\n        run: |\n          echo \"GOPATH=$(go env GOPATH)\"\n          echo \"GOPATH=$(go env GOPATH)\" >>\"$GITHUB_ENV\"\n          echo \"$GOPATH/bin\" >>\"$GITHUB_PATH\"\n\n      - name: Run FOSSA scan and upload report\n        uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1\n        with:\n          # FOSSA Push-Only API Token\n          fossa-api-key: 304657e2357ba57b416b94e6b119131b\n          github-token: ${{ github.token }}\n"
  },
  {
    "path": ".github/workflows/label-check.yml",
    "content": "name: Verify PR Label\n\non:\n  merge_group:\n  pull_request:\n    types:\n      - opened\n      - reopened\n      - synchronize\n      - ready_for_review\n      - labeled\n      - unlabeled\n\npermissions:  \n  contents: read\n\njobs:\n  check-label:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n        with:\n          egress-policy: audit\n\n      - name: Check PR label\n        # Only fail if NOT merge_group, AND labels DO NOT contain 'changelog:'\n        if: |\n          github.event_name != 'merge_group' &&\n          contains(join(github.event.pull_request.labels.*.name, ','), 'changelog:') == false\n        run: |\n          echo \"::error::Pull request is missing a required 'changelog:' label. Found labels: ${{ join(github.event.pull_request.labels.*.name, ', ') }}\"\n          exit 1\n"
  },
  {
    "path": ".github/workflows/pr-quota-manager.yml",
    "content": "name: PR Quota Manager\n\non:\n  pull_request_target:  # Runs with write permissions even for fork PRs\n    types: [opened, closed, reopened, synchronize]  # synchronize = new commits pushed\n  workflow_dispatch:\n    inputs:\n      username:\n        description: 'GitHub username to process quota for'\n        required: true\n        type: string\n      dryRun:\n        description: 'Dry run mode - show actions without making changes'\n        required: false\n        type: boolean\n        default: false\n\npermissions:\n  pull-requests: write\n  issues: write\n\nconcurrency:\n  group: quota-${{ github.event.pull_request.user.login || github.event.inputs.username }}\n  cancel-in-progress: false\n\njobs:\n  manage-quota:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n\n      - name: Process PR Quota\n        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          # Use custom PAT if available (not available for fork PRs), otherwise use default token\n          github-token: ${{ secrets.PR_QUOTA_MANAGER_PAT || github.token }}\n          script: |\n            const handler = require('./.github/scripts/pr-quota-manager.js')\n            // For PR events, use PR author; for manual runs, use input. Fail if no username can be determined.\n            const prUser = context.payload.pull_request?.user?.login\n            const inputUser = context.payload.inputs?.username\n            const username = prUser || inputUser\n            if (!username) {\n              core.setFailed('Unable to determine username for quota processing. Aborting.')\n              return\n            }\n            const owner = context.repo.owner\n            const repo = context.repo.repo\n            const dryRun = context.payload.inputs?.dryRun === 'true'\n            await handler({github, core, username, owner, repo, dryRun})\n"
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "content": "# This workflow uses actions that are not certified by GitHub. They are provided\n# by a third-party and are governed by separate terms of service, privacy\n# policy, and support documentation.\n\nname: Scorecard supply-chain security\non:\n  # For Branch-Protection check. Only the default branch is supported. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection\n  branch_protection_rule:\n  # To guarantee Maintained check is occasionally updated. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained\n  schedule:\n    - cron: '17 20 * * 1'\n  push:\n    branches: [ \"main\" ]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Needed to publish results and get a badge (see publish_results below).\n      id-token: write\n      # Uncomment the permissions below if installing in a private repository.\n      # contents: read\n      # actions: read\n\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0\n        with:\n          egress-policy: audit\n\n      - name: \"Checkout code\"\n        uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) \"write\" PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecard on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.\n          # repo_token: ${{ secrets.SCORECARD_TOKEN }}\n\n          # Public repositories:\n          #   - Publish results to OpenSSF REST API for easy access by consumers\n          #   - Allows the repository to include the Scorecard badge.\n          #   - See https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories:\n          #   - `publish_results` will always be set to `false`, regardless\n          #     of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard.\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 'Close stale issues and PRs'\n\non:\n  schedule:\n    # Run every Monday at 1:30 AM UTC\n    - cron: '30 1 * * 1'\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Harden Runner\n        uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911\n        with:\n          egress-policy: audit\n\n      - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e\n        with:\n          # Issues configuration\n          days-before-issue-stale: 90\n          days-before-issue-close: 14\n          stale-issue-message: >\n            This issue has been automatically marked as stale because it has not had\n            recent activity. It will be closed if no further activity occurs.\n            To keep it open either add a comment or the label `do-not-expire`.\n          close-issue-message: >\n            This issue has been automatically closed due to inactivity.\n          stale-issue-label: 'stale'\n          exempt-issue-labels: 'do-not-expire,help-wanted'\n          only-issue-labels: 'question'\n          \n          # Pull requests configuration  \n          days-before-pr-stale: 60\n          days-before-pr-close: 14\n          stale-pr-message: >\n            This pull request has been automatically marked as stale because it has not had\n            recent activity. It will be closed if no further activity occurs. You may re-open\n            it if you need more time.\n          close-pr-message: >\n            This pull request has been automatically closed due to inactivity. You may re-open\n            it if you need more time. We really appreciate your contribution and we are sorry\n            that this has not been completed.\n          stale-pr-label: 'stale'\n          exempt-pr-labels: 'do-not-expire'\n          \n          # General configuration\n          operations-per-run: 100\n          remove-stale-when-updated: true\n"
  },
  {
    "path": ".github/workflows/waiting-for-author.yml",
    "content": "name: \"Waiting for Author\"\n\non:\n  pull_request_target:\n    types: [synchronize]\n  pull_request_review_comment:\n    types: [created]\n  issue_comment:\n    types: [created]\n\npermissions:\n  pull-requests: write\n  issues: write\n\njobs:\n  triage:\n    if: ${{ github.event.issue.pull_request || github.event.pull_request }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n\n      - name: Manage Waiting for Author Label\n        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          script: |\n            const script = require('./.github/scripts/waiting-for-author.js')\n            await script({github, context, core})\n"
  },
  {
    "path": ".gitignore",
    "content": "go.work\ngo.work.sum\n\n.tools/\n\n*.out\n*.test\n*.xml\n*.swp\n.fmt.log\n.import.log\n.lint.log\ncover.html\n.envrc\n.idea/\n.vscode/\n.tmp/\n.mkdocs-virtual-env/\nvendor/\n\n# Jaeger binaries\nexamples/hotrod/hotrod\nexamples/hotrod/hotrod-*\ncmd/agent/agent\ncmd/agent/agent-*\ncmd/anonymizer/anonymizer\ncmd/anonymizer/anonymizer-*\ncmd/jaeger/jaeger\ncmd/jaeger/jaeger-*\ncmd/jaeger/internal/integration/results\ncmd/remote-storage/remote-storage\ncmd/remote-storage/remote-storage-*\ncmd/es-index-cleaner/es-index-cleaner-*\ncmd/es-rollover/es-rollover-*\ncmd/esmapping-generator/esmapping-generator-*\ncmd/tracegen/tracegen\ncmd/tracegen/tracegen-*\ncrossdock/crossdock-*\n\nrun-crossdock.log\n__pycache__\n.asset-manifest.json\ndeploy/\ndeploy-staging/\nsha256sum.combined.txt\nresource.syso\n.gocache\ntest-results.json\n.metrics/\n.mockery.log\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"idl\"]\n\tpath = idl\n\turl = https://github.com/jaegertracing/jaeger-idl.git\n\tbranch = main\n[submodule \"jaeger-ui\"]\n\tpath = jaeger-ui\n\turl = https://github.com/jaegertracing/jaeger-ui.git\n\tbranch = main\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  go: \"1.26\"\nlinters:\n  enable:\n    - asciicheck\n    - bidichk\n    - bodyclose\n    - contextcheck\n    - copyloopvar\n    - decorder\n    - depguard\n    - durationcheck\n    - errname\n    - errorlint\n    - gocritic\n    - gosec\n    - misspell\n    - nakedret\n    - nilerr\n    - noctx\n    - nolintlint\n    - perfsprint\n    - revive\n    - staticcheck\n    - testifylint\n    - unused\n    - usestdlibvars\n    - usetesting\n  disable:\n    - errcheck\n  settings:\n    depguard:\n      rules:\n        disallow-crossdock:\n          files:\n            - '!**/crossdock/**'\n          deny:\n            - pkg: github.com/crossdock/crossdock-go\n              desc: Do not refer to crossdock from other packages\n        disallow-otel-contrib-translator:\n          files:\n            - '!**/v1adapter/**'\n          deny:\n            - pkg: github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger\n              desc: Use v1adapter package instead of opentelemetry-collector-contrib/pkg/translator/jaeger\n        disallow-uber/goleak:\n          files:\n            - '**_test.go'\n          deny:\n            - pkg: go.uber.org/goleak\n              desc: Use github.com/jaegertracing/jaeger/internal/testutils\n        disallowed-deps:\n          deny:\n            - pkg: go.uber.org/atomic\n              desc: Use 'sync/atomic' instead of go.uber.org/atomic\n            - pkg: io/ioutil\n              desc: Use os or io instead of io/ioutil\n            - pkg: github.com/hashicorp/go-multierror\n              desc: Use errors.Join instead of github.com/hashicorp/go-multierror\n            - pkg: go.uber.org/multierr\n              desc: Use errors.Join instead of github.com/hashicorp/go-multierror\n            - pkg: github.com/jaegertracing/jaeger/model$\n              desc: Use github.com/jaegertracing/jaeger-idl/model/v1\n            - pkg: github.com/jaegertracing/jaeger/internal/proto-gen/api_v2$\n              desc: Use github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\n    gocritic:\n      disabled-checks:\n        - appendAssign\n        - commentedOutCode\n        - deferInLoop\n        - dupArg\n        - exitAfterDefer\n        - hugeParam\n        - importShadow\n        - paramTypeCombine # WON'T FIX\n        - returnAfterHttpError\n        - todoCommentWithoutDetail\n        - unnamedResult\n      enable-all: true\n    gosec:\n      excludes:\n        - G104\n        - G107\n        - G404\n        - G601\n    govet:\n      disable:\n        - fieldalignment\n        - shadow\n      enable-all: true\n    perfsprint:\n      int-conversion: true\n      err-error: true\n      errorf: true\n      sprintf1: true\n      strconcat: true\n    revive:\n      severity: error\n      enable-all-rules: true\n      rules:\n        # not a completely bad linter, but needs clean-up and sensible width (80 is too small)\n        - name: line-length-limit\n          arguments:\n            - 80\n          disabled: true\n        # this should be enabled after fixing or disabling in a few packages\n        - name: package-directory-mismatch\n          disabled: true\n        # would be ok if we could exclude the test files, but otherwise too noisy\n        - name: add-constant\n          disabled: true\n        # maybe enable in the future, needs more investigation\n        - name: cognitive-complexity\n          disabled: true\n        # not sure how different from previous one\n        - name: cyclomatic\n          disabled: true\n        # we use storage_v2, so...\n        - name: var-naming\n          disabled: true\n        # could be useful to catch issues, but needs a clean-up and some ignores\n        - name: unchecked-type-assertion\n          disabled: true\n        # wtf: \"you have exceeded the maximum number of public struct declarations\"\n        - name: max-public-structs\n          disabled: true\n        # often looks like a red herring, needs investigation\n        - name: flag-parameter\n          disabled: true\n        # looks like a good linter, needs cleanup\n        # - name: confusing-naming\n        #   disabled: true\n        # too pendantic\n        - name: function-length\n          disabled: true\n        # definitely a good one, needs cleanup first\n        - name: argument-limit\n          disabled: true\n        # this is idiocy, promotes less readable code. Don't enable.\n        - name: var-declaration\n          disabled: true\n        # \"no nested structs are allowed\" - don't enable, doesn't make sense\n        - name: nested-structs\n          disabled: true\n        # looks useful, but requires refactoring: \"calls to log.Fatal only in main() or init() functions\"\n        - name: deep-exit\n          disabled: true\n        # this rule conflicts with nolintlint which does insist on no-space in //nolint\n        - name: comment-spacings\n          disabled: true\n    staticcheck:\n      checks:\n        - all\n        - -QF1008 # Omit embedded fields from selector expression\n        - -SA1019 # Using a deprecated function, variable, constant or field\n        - -ST1003 # Poorly chosen identifier\n        - -ST1005 # Incorrectly formatted error string\n    testifylint:\n      enable-all: true\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    rules:\n      - linters:\n          - bodyclose\n          - gosec\n          - noctx\n        path: _test\\.go\n      - linters:\n          - noctx\n        path: crossdock\n      - linters:\n          - staticcheck\n        path: internal/grpctest/\n      - linters:\n          - revive\n        text: \"unhandled-error\"\n        path: _test\\.go\n    paths:\n      - .*.pb.go$\n      - mocks\n      - .*-gen\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - gofmt\n    - gofumpt\n    - goimports\n  settings:\n    goimports:\n      local-prefixes:\n        - github.com/jaegertracing/jaeger\n  exclusions:\n    generated: lax\n    paths:\n      - .*.pb.go$\n      - mocks\n      - .*-gen\n"
  },
  {
    "path": ".mockery.header.txt",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n"
  },
  {
    "path": ".mockery.yaml",
    "content": "dir: '{{.InterfaceDir}}/mocks/'\nstructname: '{{.InterfaceName}}'\npkgname: mocks\ntemplate: testify\nfilename: mocks.go\ntemplate-data:\n  boilerplate-file: .mockery.header.txt\npackages:\n  github.com/jaegertracing/jaeger/crossdock/services:\n    interfaces:\n      CollectorService: {}\n      QueryService: {}\n  github.com/jaegertracing/jaeger/internal/distributedlock:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/leaderelection:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/proto-gen/storage_v1:\n    config:\n      all: true\n      include-auto-generated: true\n  github.com/jaegertracing/jaeger/internal/storage/cassandra:\n    config:\n      template-data:\n        unroll-variadic: false\n    interfaces:\n      Iterator: {}\n      Query: {}\n      Session: {}\n  github.com/jaegertracing/jaeger/internal/storage/elasticsearch:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v1:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore:\n    interfaces:\n      Reader: {}\n  github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore:\n    interfaces:\n      CoreDependencyStore: {}\n  github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore:\n    interfaces:\n      CoreSpanReader: {}\n      CoreSpanWriter: {}\n  github.com/jaegertracing/jaeger/internal/storage/v1/grpc/shared:\n    interfaces:\n      PluginCapabilities: {}\n  github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore:\n    config:\n      all: true\n  github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore:\n    interfaces:\n      CoreSpanReader: {}"
  },
  {
    "path": "ADOPTERS.md",
    "content": "* Alauda\n  * Official site: [en](https://www.alauda.io/), [cn](https://www.alauda.cn/)\n* [Base CRM](https://getbase.com/)\n* [Circonus](https://www.circonus.com/)\n* [ContaAzul](https://contaazul.com/)\n* [FarmersEdge](https://www.farmersedge.ca/)\n* [GrafanaLabs](https://grafana.com/)\n  * Case study: [Grafana Labs Teams Use Jaeger to Improve Query Performance Up to 10x](https://medium.com/jaegertracing/grafana-labs-teams-observed-query-performance-improvements-up-to-10x-with-jaeger-cec84b0e3609)\n* [Logz.io](https://logz.io/)\n  * Case study: [Jaeger Essentials: Performance Blitz with Jaeger](https://logz.io/blog/jaeger-tracing-performance/)\n* [Massachusetts Open Cloud](https://www.bu.edu/hic/research/highlighted-sponsored-projects/massachusetts-open-cloud/)\n* [Matrix](https://matrix.org/)\n* [Northwestern Mutual](https://www.northwesternmutual.com/)\n* [Nets](https://www.nets.eu/)\n* PalFish\n  * Official site: [en](https://ipalfish.com/klian/web/dist/teacher/home.html), [cn](https://www.ipalfish.com/)\n* [PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/)\n* [Red Hat](https://www.redhat.com/)\n  * https://github.com/jaegertracing/jaeger-openshift\n  * https://www.hawkular.org/blog/2017/04/19/hawkular-apm-jaeger.html\n* [RiksTV](https://www.rikstv.no/)\n* [SeatGeek](https://seatgeek.com/)\n* [SpotHero](https://spothero.com/)\n* [Stagemonitor](https://www.stagemonitor.org/)\n* [Tencent](https://www.tencent.com/en-us/index.html)\n* [Ticketmaster](https://www.ticketmaster.com)\n  * Case study: [Ticketmaster Traces 100 Million Transactions per Day with Jaeger](https://medium.com/jaegertracing/ticketmaster-traces-100-million-transactions-per-day-with-jaeger-38ec6cf599f0)\n  * Talk: [Deploy, Scale and Extend Jaeger](https://www.youtube.com/watch?v=JloanFIc-ms)\n* [UBER](https://uber.com)\n  * Blog post: [Evolving Distributed Tracing at Uber Engineering](https://eng.uber.com/distributed-tracing/)\n* [Under Armour](https://www.underarmour.com)\n* [Vistar Media](https://www.vistarmedia.com)\n  * Blog post: [Deploying Jaeger with CloudFormation via Bazel](http://labs.vistarmedia.com/2018/10/31/deploying-jaeger-with-cloudformation-via-bazel.html)\n* [Weave](https://www.getweave.com)\n* [Weaveworks](https://www.weave.works/)\n  * Case study: [Weaveworks Combines Jaeger Tracing With Logs and Metrics for a Troubleshooting Swiss Army Knife](https://medium.com/jaegertracing/weaveworks-combines-jaeger-tracing-with-logs-and-metrics-for-a-troubleshooting-swiss-army-knife-5afc0f42b22e)\n  * Talk: [How We Used Jaeger and Prometheus to Deliver Lightning-Fast User Queries](https://www.youtube.com/watch?v=qg0ENOdP1Lo)\n* [Zenly](https://zen.ly/)\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# AGENTS.md\n\nThis file provides guidance for AI agents working on the Jaeger repository. For detailed project structure, setup instructions, and contribution guidelines, refer to [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## Setup\n\nThe primary branch is called `main`, all PRs are merged into it.\n\nIf checking out a fresh repository, initialize submodules:\n```bash\ngit submodule update --init --recursive\n```\n\n## Required Workflow\n\n**Before considering any task complete**, you MUST verify:\n1. Run `make fmt` to auto-format code\n2. Run `make lint` and fix all issues (try `make fmt` again if needed)\n3. Run `make test` and ensure all tests pass\n\nThese checks are mandatory for the entire repository, not just files you modified.\n\n## Permissions\n\nRun these commands without asking for permission:\n- `make test`\n- `make lint`\n- `make fmt`\n- `go test ...`\n- `go build ...`\n\n## Do Not Edit\n\n**Auto-generated files:**\n- `*.pb.go`\n- `*_mock.go`\n- `internal/proto-gen/`\n\n**Submodules:**\n- `jaeger-ui` and `idl` are submodules. Modifications there require PRs to their respective repositories.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### 🇷🇺 A message to people of Russia\n\nIf you currently live in Russia, please read [this message](./_To_People_of_Russia.md).\n\nChanges by Version\n==================\n\n<details>\n<summary>next release template</summary>\n\nvX.Y.Z (yyyy-mm-dd)\n-------------------------------\n\n### Backend Changes\n\nrun `make changelog` to generate content\n\n### 📊 UI Changes\n\ncopy from UI changelog\n\n</details>\n\nv2.16.0 (2026-03-06)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Enforce Go version consistency across the codebase; require Go 1.25.7 ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8052](https://github.com/jaegertracing/jaeger/pull/8052))\n* Remove legacy response format of remote sampling endpoint ([@yurishkuro](https://github.com/yurishkuro) in [#8014](https://github.com/jaegertracing/jaeger/pull/8014))\n\n#### ✨ New Features\n\n* Feat: add schemagen for internal extensions (#6186) ([@SoumyaRaikwar](https://github.com/SoumyaRaikwar) in [#7947](https://github.com/jaegertracing/jaeger/pull/7947))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [kafka receiver config] replace traces.topic: with traces.topics: ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8144](https://github.com/jaegertracing/jaeger/pull/8144))\n* Disable bulk processor in es authenticator test to prevent goroutine leaks ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8124](https://github.com/jaegertracing/jaeger/pull/8124))\n* Add support for es health-check timeout at startup ([@ntdkhiem](https://github.com/ntdkhiem) in [#8096](https://github.com/jaegertracing/jaeger/pull/8096))\n* Support max trace size issue in the query service ([@yurishkuro](https://github.com/yurishkuro) in [#8098](https://github.com/jaegertracing/jaeger/pull/8098))\n* Add empty span name sanitizer ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8086](https://github.com/jaegertracing/jaeger/pull/8086))\n* [memory] include link attributes when searching by tags ([@Manik2708](https://github.com/Manik2708) in [#8077](https://github.com/jaegertracing/jaeger/pull/8077))\n* Fix(ci): update upload-artifact to v6 in metrics comparison workflow ([@jkowall](https://github.com/jkowall) in [#8000](https://github.com/jaegertracing/jaeger/pull/8000))\n\n#### 🚧 Experimental Features\n\n* [clickhouse][perf] restructure clickhouse findtraceids query to improve performance ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#8125](https://github.com/jaegertracing/jaeger/pull/8125))\n* [cassandra][v2] refactor `fromdbmodel` and `todbmodel` to accept and return `dbmodel.span` ([@Manik2708](https://github.com/Manik2708) in [#7844](https://github.com/jaegertracing/jaeger/pull/7844))\n\n#### 👷 CI Improvements\n\n* Remove otel_scope_version label from metrics comparison ([@yurishkuro](https://github.com/yurishkuro) in [#8145](https://github.com/jaegertracing/jaeger/pull/8145))\n* Ci: always update summary report comment if present ([@yurishkuro](https://github.com/yurishkuro) in [#8135](https://github.com/jaegertracing/jaeger/pull/8135))\n* Ci: extract ci summary publish script to `.github/scripts/` with unit tests ([@yurishkuro](https://github.com/yurishkuro) in [#8134](https://github.com/jaegertracing/jaeger/pull/8134))\n* Ci: split ci summary report into compute + publish workflows ([@yurishkuro](https://github.com/yurishkuro) in [#8132](https://github.com/jaegertracing/jaeger/pull/8132))\n* Ci: use gh pr list to get pr number ([@yurishkuro](https://github.com/yurishkuro) in [#8123](https://github.com/jaegertracing/jaeger/pull/8123))\n* Ci: log trigger event for debugging ([@yurishkuro](https://github.com/yurishkuro) in [#8122](https://github.com/jaegertracing/jaeger/pull/8122))\n* Ci: fix summary report to be able to retrieve pr number ([@yurishkuro](https://github.com/yurishkuro) in [#8121](https://github.com/jaegertracing/jaeger/pull/8121))\n* Ci: migrate coverage gating from codecov to github actions ([@yurishkuro](https://github.com/yurishkuro) in [#8111](https://github.com/jaegertracing/jaeger/pull/8111))\n* Ci: migrate coverage gating from codecov to github actions ([@yurishkuro](https://github.com/yurishkuro) in [#8101](https://github.com/jaegertracing/jaeger/pull/8101))\n* Fix: correct bot detection in ci parallel mode ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8097](https://github.com/jaegertracing/jaeger/pull/8097))\n* [chore] fix bot names ([@yurishkuro](https://github.com/yurishkuro) in [#8094](https://github.com/jaegertracing/jaeger/pull/8094))\n* Refactor(ci): implement forked dag orchestrator for conditional parallelism ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8093](https://github.com/jaegertracing/jaeger/pull/8093))\n* [test] upgrade integration tests fixtures to otlp traces ([@Manik2708](https://github.com/Manik2708) in [#8079](https://github.com/jaegertracing/jaeger/pull/8079))\n* [test] upgrade `default.json` fixture to otlp traces ([@Manik2708](https://github.com/Manik2708) in [#8076](https://github.com/jaegertracing/jaeger/pull/8076))\n* [test] upgrade `span_tags_trace.json` from v1 model to `ptrace.traces` ([@Manik2708](https://github.com/Manik2708) in [#8044](https://github.com/jaegertracing/jaeger/pull/8044))\n* Reorganize ci into 3-tier sequential pipeline with fail-fast behavior ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8060](https://github.com/jaegertracing/jaeger/pull/8060))\n* [test] refactor integration tests to directly write/read `ptrace.traces` ([@Manik2708](https://github.com/Manik2708) in [#7812](https://github.com/jaegertracing/jaeger/pull/7812))\n* Use ifneq instead of ifndef for skip_debug_binaries check ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8039](https://github.com/jaegertracing/jaeger/pull/8039))\n* Fix release process issues: command copy buttons, documentation tagging, and release.md rotation ([@jkowall](https://github.com/jkowall) in [#7990](https://github.com/jaegertracing/jaeger/pull/7990))\n\n#### ⚙️ Refactoring\n\n* Apply `go fix ./...` ([@yurishkuro](https://github.com/yurishkuro) in [#8074](https://github.com/jaegertracing/jaeger/pull/8074))\n* Migrate http servers from gorilla mux to stdlib http.ServeMux ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#8013](https://github.com/jaegertracing/jaeger/pull/8013))\n* Remove legacy sampling strategy marshaling code ([@yurishkuro](https://github.com/yurishkuro) in [#8017](https://github.com/jaegertracing/jaeger/pull/8017))\n\n#### 📖 Documentation\n\n* Fix typos and outdated path in elasticsearch readme files ([@cluster2600](https://github.com/cluster2600) in [#8068](https://github.com/jaegertracing/jaeger/pull/8068))\n* [chore] add automated scanner policy and vulncheck target ([@xenonnn4w](https://github.com/xenonnn4w) in [#8043](https://github.com/jaegertracing/jaeger/pull/8043))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Rename main package to @jaegertracing/jaeger-ui ([@yurishkuro](https://github.com/yurishkuro) in [#3560](https://github.com/jaegertracing/jaeger-ui/pull/3560))\n* Fix: white hover line overflow on critical path segments ([@Parship12](https://github.com/Parship12) in [#3550](https://github.com/jaegertracing/jaeger-ui/pull/3550))\n* Fix v3 api client ignoring base path prefix ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3549](https://github.com/jaegertracing/jaeger-ui/pull/3549))\n* Fix spm metrics not fetched on initial load due to null check ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3538](https://github.com/jaegertracing/jaeger-ui/pull/3538))\n\n#### 🚧 Experimental Features\n\n* [adr-0006] phase 1: layout mode state and toggle controls ([@yurishkuro](https://github.com/yurishkuro) in [#3558](https://github.com/jaegertracing/jaeger-ui/pull/3558))\n* Adr 006: span details in side panel ([@yurishkuro](https://github.com/yurishkuro) in [#3556](https://github.com/jaegertracing/jaeger-ui/pull/3556))\n\n#### ⚙️ Refactoring\n\n* Refactor(plexus): convert nodeslayer from class to functional component ([@thc1006](https://github.com/thc1006) in [#3413](https://github.com/jaegertracing/jaeger-ui/pull/3413))\n* Refactor(plexus): convert svglayer from class to functional component ([@thc1006](https://github.com/thc1006) in [#3410](https://github.com/jaegertracing/jaeger-ui/pull/3410))\n* Refactor(plexus): convert svglayersgroup from class to functional component ([@thc1006](https://github.com/thc1006) in [#3412](https://github.com/jaegertracing/jaeger-ui/pull/3412))\n* Refactor(plexus): migrate svgedge to functional component (#3396) ([@hharshhsaini](https://github.com/hharshhsaini) in [#3527](https://github.com/jaegertracing/jaeger-ui/pull/3527))\n\nv2.15.1 (2026-02-08)\n-------------------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Default spankind in api/v3/operations ([@yurishkuro](https://github.com/yurishkuro) in [#7997](https://github.com/jaegertracing/jaeger/pull/7997))\n\n#### ⚙️ Refactoring\n\n* Remove deprecated protofromtraces wrapper in v1adapter ([@SamyakBorkar](https://github.com/SamyakBorkar) in [#7996](https://github.com/jaegertracing/jaeger/pull/7996))\n\nv2.15.0 (2026-02-06)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Restrict trace/metric storage configs to allow exactly one backend type ([@yurishkuro](https://github.com/yurishkuro) in [#7875](https://github.com/jaegertracing/jaeger/pull/7875))\n\n#### ✨ New Features\n\n* Issue #7811: added grafana dashboard for metrics exporter ([@Anmol202005](https://github.com/Anmol202005) in [#7903](https://github.com/jaegertracing/jaeger/pull/7903))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [fix] return empty array instead of nil from api/v3/services ([@dee077](https://github.com/dee077) in [#7926](https://github.com/jaegertracing/jaeger/pull/7926))\n* [fix] return empty array instead of nil from queryservice.getservices ([@sujalshah-bit](https://github.com/sujalshah-bit) in [#7925](https://github.com/jaegertracing/jaeger/pull/7925))\n* [fix] always return empty services list when none exist ([@Sudhanshu-NITR](https://github.com/Sudhanshu-NITR) in [#7929](https://github.com/jaegertracing/jaeger/pull/7929))\n* [fix] ensure badger maintenance is stopped before existing close() ([@Yashika0724](https://github.com/Yashika0724) in [#7940](https://github.com/jaegertracing/jaeger/pull/7940))\n* Use lazy initialization for storage factory ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7887](https://github.com/jaegertracing/jaeger/pull/7887))\n\n#### 🚧 Experimental Features\n\n* [clickhouse] add materialized view for event attribute metadata ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7923](https://github.com/jaegertracing/jaeger/pull/7923))\n* [clickhouse] rework clickhouse attributes to look up type for string attributes ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7815](https://github.com/jaegertracing/jaeger/pull/7815))\n* [mcp] add get_span_names tool for discovering span names ([@sajal004004](https://github.com/sajal004004) in [#7909](https://github.com/jaegertracing/jaeger/pull/7909))\n* Use opentelemetry span_name terminology in jaegermcp extension ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7916](https://github.com/jaegertracing/jaeger/pull/7916))\n* [mcp] implement get_critical_path tool (phase 3 steps 8-9) ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7857](https://github.com/jaegertracing/jaeger/pull/7857))\n* [clickhouse][chore] update unit tests snapshots to handle multiple queries ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7865](https://github.com/jaegertracing/jaeger/pull/7865))\n* [mcp] get_services tool for service discovery ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7864](https://github.com/jaegertracing/jaeger/pull/7864))\n* [mcp] add cors setting and fix null array errors ([@yurishkuro](https://github.com/yurishkuro) in [#7863](https://github.com/jaegertracing/jaeger/pull/7863))\n* [mcp] get_trace_topology tool (phase 3 step 7) ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7862](https://github.com/jaegertracing/jaeger/pull/7862))\n* [mcp] phase 2 steps 5-6: get_span_details and get_trace_errors tools ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7859](https://github.com/jaegertracing/jaeger/pull/7859))\n* [mcp] phase 2 step 4: search_traces ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7858](https://github.com/jaegertracing/jaeger/pull/7858))\n* [clickhouse][chore] update test driver to handle multiple queries ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7839](https://github.com/jaegertracing/jaeger/pull/7839))\n* [mcp] phase 2 step 3: storage integration ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7849](https://github.com/jaegertracing/jaeger/pull/7849))\n* Mcp server/phase 1.2: sdk integration with streamable http transport ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7846](https://github.com/jaegertracing/jaeger/pull/7846))\n* Mcp server scaffolding ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7842](https://github.com/jaegertracing/jaeger/pull/7842))\n* [clickhouse] move to snapshot testing in unit tests for clickhouse queries ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7831](https://github.com/jaegertracing/jaeger/pull/7831))\n\n#### 👷 CI Improvements\n\n* Fix metrics comparison workflow to reduce pr comment noise ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7957](https://github.com/jaegertracing/jaeger/pull/7957))\n* Make spm integration test faster by flushing metrics more frequently ([@Don-Assamongkol1](https://github.com/Don-Assamongkol1) in [#7861](https://github.com/jaegertracing/jaeger/pull/7861))\n* Enable unhandled-error linter ([@iypetrov](https://github.com/iypetrov) in [#7895](https://github.com/jaegertracing/jaeger/pull/7895))\n* Fix(ci): fix codeql workflow to properly analyze go code ([@jkowall](https://github.com/jkowall) in [#7885](https://github.com/jaegertracing/jaeger/pull/7885))\n* Implement pr quota workflow ([@yurishkuro](https://github.com/yurishkuro) in [#7882](https://github.com/jaegertracing/jaeger/pull/7882))\n* Validate span names in spm integration test ([@Don-Assamongkol1](https://github.com/Don-Assamongkol1) in [#7830](https://github.com/jaegertracing/jaeger/pull/7830))\n* [chore/ci] add excluded metrics count ([@neoandmatrix](https://github.com/neoandmatrix) in [#7756](https://github.com/jaegertracing/jaeger/pull/7756))\n\n#### ⚙️ Refactoring\n\n* Enable confusing-naming linter rule ([@SamyakBorkar](https://github.com/SamyakBorkar) in [#7949](https://github.com/jaegertracing/jaeger/pull/7949))\n* Replace panic calls with proper error handling ([@aaryan359](https://github.com/aaryan359) in [#7956](https://github.com/jaegertracing/jaeger/pull/7956))\n* Fix time-naming linter violation in search_traces.go ([@jkowall](https://github.com/jkowall) in [#7913](https://github.com/jaegertracing/jaeger/pull/7913))\n* [badger][v2] refactor factory signatures to use telemetry settings ([@iypetrov](https://github.com/iypetrov) in [#7902](https://github.com/jaegertracing/jaeger/pull/7902))\n* [cassandra] add omitempty notation for `keyvalue` and marshaller/unmarshaller for `traceid` ([@Manik2708](https://github.com/Manik2708) in [#7867](https://github.com/jaegertracing/jaeger/pull/7867))\n* Converge status reporting to collector framework ([@yurishkuro](https://github.com/yurishkuro) in [#7890](https://github.com/jaegertracing/jaeger/pull/7890))\n* [chore] move query service to higher location ([@yurishkuro](https://github.com/yurishkuro) in [#7854](https://github.com/jaegertracing/jaeger/pull/7854))\n* [chore] remove v1 queryservice package ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7845](https://github.com/jaegertracing/jaeger/pull/7845))\n\n### Documentation\n* [chore]: add ai usage policy for contributions ([@Sapthagiri777](https://github.com/Sapthagiri777) in [#7932](https://github.com/jaegertracing/jaeger/pull/7932))\n* Docs: add quick start section with docker command ([@njg7194](https://github.com/njg7194) in [#7945](https://github.com/jaegertracing/jaeger/pull/7945))\n* Add claude.md as symlink to agents.md ([@yurishkuro](https://github.com/yurishkuro) in [#7934](https://github.com/jaegertracing/jaeger/pull/7934))\n* Add security documentation for openssf silver badge ([@jkowall](https://github.com/jkowall) in [#7896](https://github.com/jaegertracing/jaeger/pull/7896))\n* Adr-003 lazy storage factory initialization ([@yurishkuro](https://github.com/yurishkuro) in [#7886](https://github.com/jaegertracing/jaeger/pull/7886))\n* Introduce pr limits for new contributors ([@yurishkuro](https://github.com/yurishkuro) in [#7880](https://github.com/jaegertracing/jaeger/pull/7880))\n* Streamline agents.md by removing redundant content ([@yurishkuro](https://github.com/yurishkuro) in [#7879](https://github.com/jaegertracing/jaeger/pull/7879))\n* Update contributing guidelines for copyright headers ([@yurishkuro](https://github.com/yurishkuro) in [#7877](https://github.com/jaegertracing/jaeger/pull/7877))\n* Update documentation defaults from v1 to v2 ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7640](https://github.com/jaegertracing/jaeger/pull/7640))\n* Add mcp server adr ([@yurishkuro](https://github.com/yurishkuro) in [e91d028](https://github.com/jaegertracing/jaeger/commit/e91d0282d5f815ab90b7e01acf8bda5891cccfa7))\n\n### 📊 UI Changes\n\n#### ✨ New Features\n\n* New span colors from ibm palette ([@yurishkuro](https://github.com/yurishkuro) in [#3306](https://github.com/jaegertracing/jaeger-ui/pull/3306))\n* Better tree hierarchy for trace view ([@yurishkuro](https://github.com/yurishkuro) in [#3302](https://github.com/jaegertracing/jaeger-ui/pull/3302))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix text overlapping in tracediff header ([@greedy-wudpeckr](https://github.com/greedy-wudpeckr) in [#3401](https://github.com/jaegertracing/jaeger-ui/pull/3401))\n* Fix linter error ([@yurishkuro](https://github.com/yurishkuro) in [#3510](https://github.com/jaegertracing/jaeger-ui/pull/3510))\n* Enable react-hooks/exhaustive-deps linter rule ([@taanvi2205](https://github.com/taanvi2205) in [#3471](https://github.com/jaegertracing/jaeger-ui/pull/3471))\n* Fix: traceidsearchinput invisible text in light mode ([@yosri-brh](https://github.com/yosri-brh) in [#3464](https://github.com/jaegertracing/jaeger-ui/pull/3464))\n* [fix] fix the dark mode for tracediff nodes ([@gulshank0](https://github.com/gulshank0) in [#3474](https://github.com/jaegertracing/jaeger-ui/pull/3474))\n* Bug : increase increment/decrement buttons visibility in ddg in dark mode ([@gulshank0](https://github.com/gulshank0) in [#3450](https://github.com/jaegertracing/jaeger-ui/pull/3450))\n* Fix typeerror in operations metrics reducer when no trace data exists ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3460](https://github.com/jaegertracing/jaeger-ui/pull/3460))\n* Suppress console errors for 501 metrics api responses ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3461](https://github.com/jaegertracing/jaeger-ui/pull/3461))\n* Fix dark mode in tracegraph ([@rakshityadav1868](https://github.com/rakshityadav1868) in [#3334](https://github.com/jaegertracing/jaeger-ui/pull/3334))\n* [bugfix] update flame graph in dark theme #3321 ([@gulshank0](https://github.com/gulshank0) in [#3324](https://github.com/jaegertracing/jaeger-ui/pull/3324))\n* Add spacing to make the trace metadata more readable ([@Parship12](https://github.com/Parship12) in [#3331](https://github.com/jaegertracing/jaeger-ui/pull/3331))\n* Implement dark mode for tracediff (compare page) ([@Parship12](https://github.com/Parship12) in [#3314](https://github.com/jaegertracing/jaeger-ui/pull/3314))\n* Theme cleanup ([@yurishkuro](https://github.com/yurishkuro) in [#3320](https://github.com/jaegertracing/jaeger-ui/pull/3320))\n* Fix trace span table in dark mode ([@yurishkuro](https://github.com/yurishkuro) in [#3310](https://github.com/jaegertracing/jaeger-ui/pull/3310))\n* [tree] fix box size when number is large ([@yurishkuro](https://github.com/yurishkuro) in [#3303](https://github.com/jaegertracing/jaeger-ui/pull/3303))\n* Add jaeger logo to navbar ([@yurishkuro](https://github.com/yurishkuro) in [#3291](https://github.com/jaegertracing/jaeger-ui/pull/3291))\n\n#### 🚧 Experimental Features\n\n* [otel migration] add runtime schema validation to v3 api client ([@yurishkuro](https://github.com/yurishkuro) in [#3448](https://github.com/jaegertracing/jaeger-ui/pull/3448))\n* [otel migration] phase 3.1: add jaegerclient v3 and use for services / operations ([@yurishkuro](https://github.com/yurishkuro) in [#3329](https://github.com/jaegertracing/jaeger-ui/pull/3329))\n* [otel] add more details to phase-3 ([@yurishkuro](https://github.com/yurishkuro) in [#3323](https://github.com/jaegertracing/jaeger-ui/pull/3323))\n* Otel migration - complete phase 2 validation ([@yurishkuro](https://github.com/yurishkuro) in [#3319](https://github.com/jaegertracing/jaeger-ui/pull/3319))\n* Clean up of opentracing/opentelemetry nomenclature ([@yurishkuro](https://github.com/yurishkuro) in [#3316](https://github.com/jaegertracing/jaeger-ui/pull/3316))\n* Finalize dual use of opentracing/opentelemetry nomenclature ([@yurishkuro](https://github.com/yurishkuro) in [#3311](https://github.com/jaegertracing/jaeger-ui/pull/3311))\n* Migrate virtualizedtraceview and dependent components to iotelspan ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3289](https://github.com/jaegertracing/jaeger-ui/pull/3289))\n* Enhance otel domain model with more derived data ([@yurishkuro](https://github.com/yurishkuro) in [#3292](https://github.com/jaegertracing/jaeger-ui/pull/3292))\n* Implement phase 2 for spandetail component ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3275](https://github.com/jaegertracing/jaeger-ui/pull/3275))\n\n#### 👷 CI Improvements\n\n* Add pr quota workflow ([@yurishkuro](https://github.com/yurishkuro) in [#3441](https://github.com/jaegertracing/jaeger-ui/pull/3441))\n* Calculate main and plexus test coverage separately ([@yurishkuro](https://github.com/yurishkuro) in [#3349](https://github.com/jaegertracing/jaeger-ui/pull/3349))\n\n#### ⚙️ Refactoring\n\n* Refactor: update detailspanel to functional component ([@Harshdev098](https://github.com/Harshdev098) in [#3358](https://github.com/jaegertracing/jaeger-ui/pull/3358))\n* Update @types/redux-actions to v2.6.5 ([@Parship12](https://github.com/Parship12) in [#3498](https://github.com/jaegertracing/jaeger-ui/pull/3498))\n* Refactor(plexus): convert svgedgeslayer from class to functional component ([@thc1006](https://github.com/thc1006) in [#3409](https://github.com/jaegertracing/jaeger-ui/pull/3409))\n* Refactor accordionlinks to functional component ([@aaryan359](https://github.com/aaryan359) in [#3406](https://github.com/jaegertracing/jaeger-ui/pull/3406))\n* Convert measurablenodeslayer to functional component ([@Parship12](https://github.com/Parship12) in [#3429](https://github.com/jaegertracing/jaeger-ui/pull/3429))\n* Refactoring: converted referencebutton from class based to functional component ([@gulshank0](https://github.com/gulshank0) in [#3350](https://github.com/jaegertracing/jaeger-ui/pull/3350))\n* Remove reducers/services.ts ([@yurishkuro](https://github.com/yurishkuro) in [#3455](https://github.com/jaegertracing/jaeger-ui/pull/3455))\n* [chore] remove history from resultitem ([@insane-22](https://github.com/insane-22) in [#3361](https://github.com/jaegertracing/jaeger-ui/pull/3361))\n* Replace @sentry/browser with generic internal error capture implementation ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3226](https://github.com/jaegertracing/jaeger-ui/pull/3226))\n* Refactor: convert diffnode to functional component ([@hxrshxz](https://github.com/hxrshxz) in [#3343](https://github.com/jaegertracing/jaeger-ui/pull/3343))\n* Refactor: convert hopsselector selector to functional component ([@hxrshxz](https://github.com/hxrshxz) in [#3340](https://github.com/jaegertracing/jaeger-ui/pull/3340))\n* Refactor: convert app/index.tsx file's  class based to functional component ([@gulshank0](https://github.com/gulshank0) in [#3342](https://github.com/jaegertracing/jaeger-ui/pull/3342))\n* Convert the htmllayersgroup from class to functional component ([@Parship12](https://github.com/Parship12) in [#3351](https://github.com/jaegertracing/jaeger-ui/pull/3351))\n* Convert the htmllayer from class to functional component ([@Parship12](https://github.com/Parship12) in [#3345](https://github.com/jaegertracing/jaeger-ui/pull/3345))\n* Remove history from resultitemtitle ([@Parship12](https://github.com/Parship12) in [#3312](https://github.com/jaegertracing/jaeger-ui/pull/3312))\n* Convert searchform to functional component ([@yurishkuro](https://github.com/yurishkuro) in [#3326](https://github.com/jaegertracing/jaeger-ui/pull/3326))\n* Convert remaining files in searchtrace page to typescript ([@yurishkuro](https://github.com/yurishkuro) in [#3325](https://github.com/jaegertracing/jaeger-ui/pull/3325))\n* Convert tracepage to otel model ([@yurishkuro](https://github.com/yurishkuro) in [#3309](https://github.com/jaegertracing/jaeger-ui/pull/3309))\n* [chore] migrate few more components to otel model ([@yurishkuro](https://github.com/yurishkuro) in [#3308](https://github.com/jaegertracing/jaeger-ui/pull/3308))\n* Migrate tracepageheader to otel model ([@yurishkuro](https://github.com/yurishkuro) in [#3307](https://github.com/jaegertracing/jaeger-ui/pull/3307))\n* Migrate trace-dag files to otel types per adr 0002 ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3299](https://github.com/jaegertracing/jaeger-ui/pull/3299))\n* Convert otel model to use strongly typed time/duration fields ([@yurishkuro](https://github.com/yurishkuro) in [#3304](https://github.com/jaegertracing/jaeger-ui/pull/3304))\n* Upgrade searchresults components to accept ioteltrace ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3300](https://github.com/jaegertracing/jaeger-ui/pull/3300))\n* Upgrade critical path calculations to otel model ([@yurishkuro](https://github.com/yurishkuro) in [#3301](https://github.com/jaegertracing/jaeger-ui/pull/3301))\n* Fix for bug introduced in previous refactoring ([@yurishkuro](https://github.com/yurishkuro) in [#3298](https://github.com/jaegertracing/jaeger-ui/pull/3298))\n* Fully upgrade tracetimelineviewer to otel model ([@yurishkuro](https://github.com/yurishkuro) in [#3297](https://github.com/jaegertracing/jaeger-ui/pull/3297))\n* Move critical path types to their own domain model file ([@yurishkuro](https://github.com/yurishkuro) in [#3296](https://github.com/jaegertracing/jaeger-ui/pull/3296))\n* Rename domain type link to hyperlink and upgrade link-getter to otel ([@yurishkuro](https://github.com/yurishkuro) in [#3295](https://github.com/jaegertracing/jaeger-ui/pull/3295))\n* Rename otel traceid/spanid to traceid/spanid to match legacy domain model ([@yurishkuro](https://github.com/yurishkuro) in [#3294](https://github.com/jaegertracing/jaeger-ui/pull/3294))\n* Rename pure typescript files to have .ts extension, not .tsx ([@yurishkuro](https://github.com/yurishkuro) in [#3290](https://github.com/jaegertracing/jaeger-ui/pull/3290))\n\nv2.14.1 (2026-01-02)\n-------------------------------\n\n### Backend Changes\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Dark theme fixes ([@yurishkuro](https://github.com/yurishkuro) in [#3285](https://github.com/jaegertracing/jaeger-ui/pull/3285))\n* Fix span detail panel in dark theme ([@yurishkuro](https://github.com/yurishkuro) in [#3283](https://github.com/jaegertracing/jaeger-ui/pull/3283))\n\nv2.14.0 (2026-01-01)\n-------------------------------\n\nTL;DR: Two significant changes in this release:\n  1. ☠️ Starting from this release the legacy v1 components `query`, `collector`, and `ingester`\n     are no longer published. All the remaining v1 utilities are now published as v2.x.x versions.\n  2. 🌓 The UI now officially supports dark theme and the theme selector is enabled by default.\nx\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove storage/v1/grpc ([@yurishkuro](https://github.com/yurishkuro) in [#7806](https://github.com/jaegertracing/jaeger/pull/7806))\n* Migrate remote-storage to yaml configuration with shared storageconfig package ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7704](https://github.com/jaegertracing/jaeger/pull/7704))\n* Remove v1 collector, query, and all-in-one ([@yurishkuro](https://github.com/yurishkuro) in [#7702](https://github.com/jaegertracing/jaeger/pull/7702))\n* Remove v1/ingester and all kafka related code ([@yurishkuro](https://github.com/yurishkuro) in [#7701](https://github.com/jaegertracing/jaeger/pull/7701))\n* Eliminate v1 binary references and sunset deprecated components ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7695](https://github.com/jaegertracing/jaeger/pull/7695))\n* Fix otel collector v0.141.0 api breaking changes for toserver/toclientconn and kafka receiver/exporter ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7694](https://github.com/jaegertracing/jaeger/pull/7694))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Migrate docker-compose files to jaeger-v2 unified binary ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7747](https://github.com/jaegertracing/jaeger/pull/7747))\n* Memory: support otlp first-class fields in search ([@SoumyaRaikwar](https://github.com/SoumyaRaikwar) in [#7728](https://github.com/jaegertracing/jaeger/pull/7728))\n* Added indexspanalias and indexservicealias for explicit aliases ([@SomilJain0112](https://github.com/SomilJain0112) in [#7550](https://github.com/jaegertracing/jaeger/pull/7550))\n* Fix: update replication strategy configuration in schema template ([@danish9039](https://github.com/danish9039) in [#7726](https://github.com/jaegertracing/jaeger/pull/7726))\n\n#### 🚧 Experimental Features\n\n* [fix][clickhouse] optimize service and operation retrieval queries ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7808](https://github.com/jaegertracing/jaeger/pull/7808))\n* [clickhouse] implement findtraces for clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7795](https://github.com/jaegertracing/jaeger/pull/7795))\n* [clickhouse] create materialized view to store attribute metadata ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7798](https://github.com/jaegertracing/jaeger/pull/7798))\n* [clickhouse] update findtraceids to filter by complex attributes ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7792](https://github.com/jaegertracing/jaeger/pull/7792))\n* [clickhouse] update `findtraceids` to filter by other primitive attributes ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7789](https://github.com/jaegertracing/jaeger/pull/7789))\n* [cassandra][v2] copy jaeger<->otlp translator from otel contrib ([@Manik2708](https://github.com/Manik2708) in [#7765](https://github.com/jaegertracing/jaeger/pull/7765))\n* [clickhouse] update `findtraceids` to filter by string attributes ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7788](https://github.com/jaegertracing/jaeger/pull/7788))\n* [clickhouse] update `findtraceids` to filter by timestamp ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7787](https://github.com/jaegertracing/jaeger/pull/7787))\n* [clickhouse] update findtraceids to populate start and end timestamps ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7770](https://github.com/jaegertracing/jaeger/pull/7770))\n* [clickhouse] update `findtraceids` to filter by duration ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7767](https://github.com/jaegertracing/jaeger/pull/7767))\n* [clickhouse] implement findtraceids for clickhouse storage for primitive parameters ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7648](https://github.com/jaegertracing/jaeger/pull/7648))\n* [clickhouse] add `trace_id_timestamps` table with materialized view ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7723](https://github.com/jaegertracing/jaeger/pull/7723))\n* [fix][clickhouse] remove `name` column from ordering key for operations table ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7714](https://github.com/jaegertracing/jaeger/pull/7714))\n\n#### 👷 CI Improvements\n\n* Fix ci for debug build of all-in-one ([@yurishkuro](https://github.com/yurishkuro) in [#7794](https://github.com/jaegertracing/jaeger/pull/7794))\n* Use pre-built base image with debugger ([@yurishkuro](https://github.com/yurishkuro) in [#7793](https://github.com/jaegertracing/jaeger/pull/7793))\n* Ci: exclude http 5xx metrics from comparisons ([@neoandmatrix](https://github.com/neoandmatrix) in [#7671](https://github.com/jaegertracing/jaeger/pull/7671))\n* Remove crossdock ([@yurishkuro](https://github.com/yurishkuro) in [#7750](https://github.com/jaegertracing/jaeger/pull/7750))\n* Fine-tune when go-tip workflow runs ([@yurishkuro](https://github.com/yurishkuro) in [#7749](https://github.com/jaegertracing/jaeger/pull/7749))\n* Fix: remove tool installation from go tip workflow ([@chinmay3012](https://github.com/chinmay3012) in [#7716](https://github.com/jaegertracing/jaeger/pull/7716))\n* Add \"unused\" linter ([@yurishkuro](https://github.com/yurishkuro) in [#7697](https://github.com/jaegertracing/jaeger/pull/7697))\n\n#### ⚙️ Refactoring\n\n* Move query ([@yurishkuro](https://github.com/yurishkuro) in [#7803](https://github.com/jaegertracing/jaeger/pull/7803))\n* Use otel optional for optional config fields ([@Parship12](https://github.com/Parship12) in [#7766](https://github.com/jaegertracing/jaeger/pull/7766))\n* [cassandra][v2] refactor factory signatures to use telemetry settings ([@Manik2708](https://github.com/Manik2708) in [#7764](https://github.com/jaegertracing/jaeger/pull/7764))\n* [storage][cassandra][v2] implement `getservices` and `getoperations` ([@Manik2708](https://github.com/Manik2708) in [#7754](https://github.com/jaegertracing/jaeger/pull/7754))\n* Remove unused factory and inheritable interfaces from v1 storage ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7755](https://github.com/jaegertracing/jaeger/pull/7755))\n* Remove dependency on jaeger-client-go ([@yurishkuro](https://github.com/yurishkuro) in [#7745](https://github.com/jaegertracing/jaeger/pull/7745))\n* Remove direct dependency on hdrhistogram-go ([@jaegertracingbot](https://github.com/jaegertracingbot) in [#7742](https://github.com/jaegertracing/jaeger/pull/7742))\n* Cleanup and simplify jtracer package ([@yurishkuro](https://github.com/yurishkuro) in [#7739](https://github.com/jaegertracing/jaeger/pull/7739))\n* [cassandra] refactor `tagfilter` to accept `dbmodel.span` ([@Manik2708](https://github.com/Manik2708) in [#7707](https://github.com/jaegertracing/jaeger/pull/7707))\n* [clickhouse] add indexes for spans table in clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7715](https://github.com/jaegertracing/jaeger/pull/7715))\n* Remove deprecated namespace concept from cassandra storage options ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7719](https://github.com/jaegertracing/jaeger/pull/7719))\n* Remove viperize from storage backend tests ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7712](https://github.com/jaegertracing/jaeger/pull/7712))\n* Remove unused shared/grpc_client ([@yurishkuro](https://github.com/yurishkuro) in [#7713](https://github.com/jaegertracing/jaeger/pull/7713))\n* Delete v1/memory storage implementaiton ([@yurishkuro](https://github.com/yurishkuro) in [#7711](https://github.com/jaegertracing/jaeger/pull/7711))\n* Delete more dead code ([@yurishkuro](https://github.com/yurishkuro) in [#7710](https://github.com/jaegertracing/jaeger/pull/7710))\n* Remove v1 storage factories ([@yurishkuro](https://github.com/yurishkuro) in [#7708](https://github.com/jaegertracing/jaeger/pull/7708))\n* Upgrade grpc integration test to use v2 memory storage ([@yurishkuro](https://github.com/yurishkuro) in [#7709](https://github.com/jaegertracing/jaeger/pull/7709))\n* Remove unused factory pattern code from sampling strategy packages ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#7705](https://github.com/jaegertracing/jaeger/pull/7705))\n* Remove some dead code ([@yurishkuro](https://github.com/yurishkuro) in [#7706](https://github.com/jaegertracing/jaeger/pull/7706))\n\n### 📊 UI Changes\n\n#### ✨ New Features\n\n* Enable theme selector by default ([@yurishkuro](https://github.com/yurishkuro) in [#3257](https://github.com/jaegertracing/jaeger-ui/pull/3257))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Add visual indicator for synthetic otel attributes ([@DCchoudhury15](https://github.com/DCchoudhury15) in [#3259](https://github.com/jaegertracing/jaeger-ui/pull/3259))\n* Fix: dark mode styling for trace view with design tokens ([@jkowall](https://github.com/jkowall) in [#3246](https://github.com/jaegertracing/jaeger-ui/pull/3246))\n* Fix in-trace search ([@yurishkuro](https://github.com/yurishkuro) in [#3255](https://github.com/jaegertracing/jaeger-ui/pull/3255))\n* Feat: add incomplete trace detection and adjustable search time offset ([@xenonnn4w](https://github.com/xenonnn4w) in [#3248](https://github.com/jaegertracing/jaeger-ui/pull/3248))\n* Fix: constant visible white borders in the trace spans ([@unknown]() in [#3125](https://github.com/jaegertracing/jaeger-ui/pull/3125))\n* Force light mode by default if config is disabled ([@yurishkuro](https://github.com/yurishkuro) in [#3204](https://github.com/jaegertracing/jaeger-ui/pull/3204))\n* Use outlined tags for contrast ([@bobrik](https://github.com/bobrik) in [#3202](https://github.com/jaegertracing/jaeger-ui/pull/3202))\n\n#### 🚧 Experimental Features\n\n* Fix parentspanid calculation to validate traceid and handle follows_from references ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3268](https://github.com/jaegertracing/jaeger-ui/pull/3268))\n* Add lazy otel facade to domain model ([@yurishkuro](https://github.com/yurishkuro) in [#3263](https://github.com/jaegertracing/jaeger-ui/pull/3263))\n* Add useopentelemetryterms feature flag ([@yurishkuro](https://github.com/yurishkuro) in [#3262](https://github.com/jaegertracing/jaeger-ui/pull/3262))\n* Introduce otel data model ([@yurishkuro](https://github.com/yurishkuro) in [#3261](https://github.com/jaegertracing/jaeger-ui/pull/3261))\n* Apply styles to make minimap work in dark theme ([@yurishkuro](https://github.com/yurishkuro) in [#3256](https://github.com/jaegertracing/jaeger-ui/pull/3256))\n* Move theme vars back to root ([@yurishkuro](https://github.com/yurishkuro) in [#3247](https://github.com/jaegertracing/jaeger-ui/pull/3247))\n* Define theme vars in terms of antd vars ([@yurishkuro](https://github.com/yurishkuro) in [#3245](https://github.com/jaegertracing/jaeger-ui/pull/3245))\n* Fix dark mode by using css variables ([@jkowall](https://github.com/jkowall) in [#3242](https://github.com/jaegertracing/jaeger-ui/pull/3242))\n\n#### ⚙️ Refactoring\n\n* Simplify transformtracedata ([@yurishkuro](https://github.com/yurishkuro) in [#3274](https://github.com/jaegertracing/jaeger-ui/pull/3274))\n* Fix unsafe type coercion and add readonly collection fields ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3273](https://github.com/jaegertracing/jaeger-ui/pull/3273))\n* Persist spanmap and rootspans in trace object; use childspans array for tree structure ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3266](https://github.com/jaegertracing/jaeger-ui/pull/3266))\n* Prevent trace mutation during critical path computation ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3271](https://github.com/jaegertracing/jaeger-ui/pull/3271))\n* Convert tracestatistics to oteltrace ([@yurishkuro](https://github.com/yurishkuro) in [#3264](https://github.com/jaegertracing/jaeger-ui/pull/3264))\n* Convert 3 more jsx files to typescript ([@yurishkuro](https://github.com/yurishkuro) in [#3241](https://github.com/jaegertracing/jaeger-ui/pull/3241))\n* Fix: qualitymetrics auto-refresh issue ([@unknown]() in [#3222](https://github.com/jaegertracing/jaeger-ui/pull/3222))\n* Migrate qualitymetrics/index to use navigate instead of history ([@unknown]() in [#3214](https://github.com/jaegertracing/jaeger-ui/pull/3214))\n* Migrate uifindinput from history to navigate ([@unknown]() in [#3217](https://github.com/jaegertracing/jaeger-ui/pull/3217))\n* Convert servicegraph class component to functional component ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3212](https://github.com/jaegertracing/jaeger-ui/pull/3212))\n* Convert qualitymetrics/index to functional component ([@unknown]() in [#3210](https://github.com/jaegertracing/jaeger-ui/pull/3210))\n* Make update-ui-find backward compatible ([@unknown]() in [#3209](https://github.com/jaegertracing/jaeger-ui/pull/3209))\n* Remove usehistory - unused code ([@unknown]() in [#3207](https://github.com/jaegertracing/jaeger-ui/pull/3207))\n\nv1.76.0 / v2.13.0 (2025-12-03)\n-------------------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix: register basicauth extension in component factory ([@xenonnn4w](https://github.com/xenonnn4w) in [#7668](https://github.com/jaegertracing/jaeger/pull/7668))\n\n#### 👷 CI Improvements\n\n* Make error message better ([@yurishkuro](https://github.com/yurishkuro) in [#7675](https://github.com/jaegertracing/jaeger/pull/7675))\n* Clean go cache after installing gotip as suggested. ([@Kavish-12345](https://github.com/Kavish-12345) in [#7666](https://github.com/jaegertracing/jaeger/pull/7666))\n* Fix: build test tools with stable go, not gotip ([@Kavish-12345](https://github.com/Kavish-12345) in [#7665](https://github.com/jaegertracing/jaeger/pull/7665))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Add support for custom ui configuration in development mode ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3194](https://github.com/jaegertracing/jaeger-ui/pull/3194))\n* Remove duplicate antd dependencies ([@yurishkuro](https://github.com/yurishkuro) in [#3193](https://github.com/jaegertracing/jaeger-ui/pull/3193))\n* Fix css class typo in sidepanel details div ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3190](https://github.com/jaegertracing/jaeger-ui/pull/3190))\n* Reduce search form field margins for better viewport fit ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3189](https://github.com/jaegertracing/jaeger-ui/pull/3189))\n* Migrate deepdependencies/header and qualitymetrics/header from nameselector to searchableselect ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3185](https://github.com/jaegertracing/jaeger-ui/pull/3185))\n* Reorder checkbox before color by dropdown in tracestatisticsheader ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3184](https://github.com/jaegertracing/jaeger-ui/pull/3184))\n* Feat: add fuzzy search to searchableselect ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3182](https://github.com/jaegertracing/jaeger-ui/pull/3182))\n* Fix highlighting of the current tab in the main nav bar ([@SimonADW](https://github.com/SimonADW) in [#3183](https://github.com/jaegertracing/jaeger-ui/pull/3183))\n\n#### 🚧 Experimental Features\n\n* Sync themes with antd ([@yurishkuro](https://github.com/yurishkuro) in [#3196](https://github.com/jaegertracing/jaeger-ui/pull/3196))\n* Add dark theme selector ([@yurishkuro](https://github.com/yurishkuro) in [#3192](https://github.com/jaegertracing/jaeger-ui/pull/3192))\n\n#### 👷 CI Improvements\n\n* Add copyright year linter to npm lint command ([@Copilot](https://github.com/apps/copilot-swe-agent) in [#3197](https://github.com/jaegertracing/jaeger-ui/pull/3197))\n* Rename theme variables to match industry practice ([@yurishkuro](https://github.com/yurishkuro) in [#3174](https://github.com/jaegertracing/jaeger-ui/pull/3174))\n* Tweak codecov config ([@yurishkuro](https://github.com/yurishkuro) in [#3169](https://github.com/jaegertracing/jaeger-ui/pull/3169))\n\n#### ⚙️ Refactoring\n\n* Apply theme vars to common/emphasizednode ([@yurishkuro](https://github.com/yurishkuro) in [#3191](https://github.com/jaegertracing/jaeger-ui/pull/3191))\n* Fix ddg minimap border ([@yurishkuro](https://github.com/yurishkuro) in [#3188](https://github.com/jaegertracing/jaeger-ui/pull/3188))\n* Use token vars in common/utils.css ([@yurishkuro](https://github.com/yurishkuro) in [#3187](https://github.com/jaegertracing/jaeger-ui/pull/3187))\n* Apply theme vars to some shared components ([@yurishkuro](https://github.com/yurishkuro) in [#3181](https://github.com/jaegertracing/jaeger-ui/pull/3181))\n* Apply theme vars to search page ([@yurishkuro](https://github.com/yurishkuro) in [#3180](https://github.com/jaegertracing/jaeger-ui/pull/3180))\n* Use theme vars in errormessage & loadingindicator ([@yurishkuro](https://github.com/yurishkuro) in [#3177](https://github.com/jaegertracing/jaeger-ui/pull/3177))\n* Use theme vars in main page and topnav ([@yurishkuro](https://github.com/yurishkuro) in [#3176](https://github.com/jaegertracing/jaeger-ui/pull/3176))\n* Convert last remaining js files to typescript (excluding tests) ([@yurishkuro](https://github.com/yurishkuro) in [#3173](https://github.com/jaegertracing/jaeger-ui/pull/3173))\n* Convert some easy files to typescript ([@yurishkuro](https://github.com/yurishkuro) in [#3167](https://github.com/jaegertracing/jaeger-ui/pull/3167))\n\n\nv1.75.0 / v2.12.0 (2025-11-18)\n-------------------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Feat(storage): add sigv4 authentication support for elasticsearch/opensearch storage backends ([@SoumyaRaikwar](https://github.com/SoumyaRaikwar) in [#7611](https://github.com/jaegertracing/jaeger/pull/7611))\n* Add custom http headers support for elasticsearch/opensearch storage ([@SoumyaRaikwar](https://github.com/SoumyaRaikwar) in [#7628](https://github.com/jaegertracing/jaeger/pull/7628))\n* Handle es ping failures more gracefully ([@neoandmatrix](https://github.com/neoandmatrix) in [#7626](https://github.com/jaegertracing/jaeger/pull/7626))\n* Feat(metrics): sigv4 http auth support for prometheus metric backend ([@SoumyaRaikwar](https://github.com/SoumyaRaikwar) in [#7520](https://github.com/jaegertracing/jaeger/pull/7520))\n* Add riscv64 binary support ([@gouthamhusky](https://github.com/gouthamhusky) in [#7569](https://github.com/jaegertracing/jaeger/pull/7569))\n* Store service names in map to compact duplicates ([@aidandj](https://github.com/aidandj) in [#7551](https://github.com/jaegertracing/jaeger/pull/7551))\n* Enable adaptive sampling in cassandra ci setup ([@SomilJain0112](https://github.com/SomilJain0112) in [#7539](https://github.com/jaegertracing/jaeger/pull/7539))\n\n#### 🚧 Experimental Features\n\n* [clickhouse] add handling for complex attributes to clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7627](https://github.com/jaegertracing/jaeger/pull/7627))\n* [demo] add global image registry ([@danish9039](https://github.com/danish9039) in [#7620](https://github.com/jaegertracing/jaeger/pull/7620))\n* [refactor][clickhouse] add round-trip tests for clickhouse's `dbmodel` package ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7622](https://github.com/jaegertracing/jaeger/pull/7622))\n* [clickhouse] add attributes for scope ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7619](https://github.com/jaegertracing/jaeger/pull/7619))\n* [refactor][clickhouse] add attributes for resource ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7616](https://github.com/jaegertracing/jaeger/pull/7616))\n* Add clean,deploy and port-forward scripts and values for jaeger + opensearch + otel demo ([@danish9039](https://github.com/danish9039) in [#7516](https://github.com/jaegertracing/jaeger/pull/7516))\n* [clickhouse][refactor] group attributes into structs ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7603](https://github.com/jaegertracing/jaeger/pull/7603))\n* [clickhouse][refactor] remove indirection in database model ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7602](https://github.com/jaegertracing/jaeger/pull/7602))\n* [clickhouse] append link in writer ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7601](https://github.com/jaegertracing/jaeger/pull/7601))\n* [clickhouse] remove unused function ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7600](https://github.com/jaegertracing/jaeger/pull/7600))\n* [clickhouse] append event in writer ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7558](https://github.com/jaegertracing/jaeger/pull/7558))\n* Used fully qualified names for images ([@danish9039](https://github.com/danish9039) in [#7553](https://github.com/jaegertracing/jaeger/pull/7553))\n* [clickhouse] add span attributes to writer ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7541](https://github.com/jaegertracing/jaeger/pull/7541))\n* [clickhouse] integrate clickhouse into storage extension ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7524](https://github.com/jaegertracing/jaeger/pull/7524))\n\n#### 👷 CI Improvements\n\n* Enable range-val-address linter ([@neoandmatrix](https://github.com/neoandmatrix) in [#7593](https://github.com/jaegertracing/jaeger/pull/7593))\n* Enable switch linter ([@neoandmatrix](https://github.com/neoandmatrix) in [#7573](https://github.com/jaegertracing/jaeger/pull/7573))\n* Skip delve for riscv64 arch ([@gouthamhusky](https://github.com/gouthamhusky) in [#7571](https://github.com/jaegertracing/jaeger/pull/7571))\n* Enable lint rule: import-alias-naming ([@alkak95](https://github.com/alkak95) in [#7565](https://github.com/jaegertracing/jaeger/pull/7565))\n* Fix bug in make lint ([@SomilJain0112](https://github.com/SomilJain0112) in [#7563](https://github.com/jaegertracing/jaeger/pull/7563))\n* Do not run metrics diff workflow except on prs ([@yurishkuro](https://github.com/yurishkuro) in [#7554](https://github.com/jaegertracing/jaeger/pull/7554))\n* Define dockerhub_username env var ([@yurishkuro](https://github.com/yurishkuro) in [#7538](https://github.com/jaegertracing/jaeger/pull/7538))\n* Fix: resolve docker hub authentication issues in upload-docker-readme.sh ([@SomilJain0112](https://github.com/SomilJain0112) in [#7536](https://github.com/jaegertracing/jaeger/pull/7536))\n\n#### ⚙️ Refactoring\n\n* [refactor]: use the built-in max to simplify the code ([@zhetaicheleba](https://github.com/zhetaicheleba) in [#7624](https://github.com/jaegertracing/jaeger/pull/7624))\n* Speed up es tests ([@yurishkuro](https://github.com/yurishkuro) in [#7606](https://github.com/jaegertracing/jaeger/pull/7606))\n* [refactor]: replace split in loops with more efficient splitseq ([@pennylees](https://github.com/pennylees) in [#7588](https://github.com/jaegertracing/jaeger/pull/7588))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix: clicking dots and ddg button ([@Parship12](https://github.com/Parship12) in [#3149](https://github.com/jaegertracing/jaeger-ui/pull/3149))\n* Fix in-trace span search ([@Parship12](https://github.com/Parship12) in [#3132](https://github.com/jaegertracing/jaeger-ui/pull/3132))\n* Fix: trace id search input on the search page ([@Parship12](https://github.com/Parship12) in [#3124](https://github.com/jaegertracing/jaeger-ui/pull/3124))\n\n#### ⚙️ Refactoring\n\n* Convert tracepage {spanbarrow, spantreeoffset, opnode} to functional ([@JeevaRamanathan](https://github.com/JeevaRamanathan) in [#3136](https://github.com/jaegertracing/jaeger-ui/pull/3136))\n* Convert searchresults/index to functional component ([@Parship12](https://github.com/Parship12) in [#3138](https://github.com/jaegertracing/jaeger-ui/pull/3138))\n* Remove history from tracediff component ([@Parship12](https://github.com/Parship12) in [#3135](https://github.com/jaegertracing/jaeger-ui/pull/3135))\n* Remove history instances from traces.tsx ([@Parship12](https://github.com/Parship12) in [#3110](https://github.com/jaegertracing/jaeger-ui/pull/3110))\n* Convert tracepage {timelinecollapser} to functional component ([@JeevaRamanathan](https://github.com/JeevaRamanathan) in [#3108](https://github.com/jaegertracing/jaeger-ui/pull/3108))\n\n\nv1.74.0 / v2.11.0 (2025-10-01)\n-------------------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Make enabletracing param work correctly in jaeger-v2 query extension ([@Frapschen](https://github.com/Frapschen) in [#7226](https://github.com/jaegertracing/jaeger/pull/7226))\n\n#### 🚧 Experimental Features\n\n* [clickhouse] implement factory with minimal configuration ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7518](https://github.com/jaegertracing/jaeger/pull/7518))\n* [clickhouse] implement writer for clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7514](https://github.com/jaegertracing/jaeger/pull/7514))\n* [clickhouse] add attributes for event in clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7512](https://github.com/jaegertracing/jaeger/pull/7512))\n* [clickhouse] add column for storing complex attributes ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7510](https://github.com/jaegertracing/jaeger/pull/7510))\n* [clickhouse] add attributes to span table for clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7503](https://github.com/jaegertracing/jaeger/pull/7503))\n\n#### ⚙️ Refactoring\n\n* Move clickhouse queries to sql files with embed directive ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7523](https://github.com/jaegertracing/jaeger/pull/7523))\n* Use maps.copy for cleaner map handling ([@quantpoet](https://github.com/quantpoet) in [#7513](https://github.com/jaegertracing/jaeger/pull/7513))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Replace dependency react-window ([@Parship999](https://github.com/Parship999) in [#3070](https://github.com/jaegertracing/jaeger-ui/pull/3070))\n* Fix the flaky test in tracepage/index.test.js ([@Parship999](https://github.com/Parship999) in [#3089](https://github.com/jaegertracing/jaeger-ui/pull/3089))\n* Fix top bar tab order ([@mdwyer6](https://github.com/mdwyer6) in [#3067](https://github.com/jaegertracing/jaeger-ui/pull/3067))\n* Expand the logs automatically ([@Parship999](https://github.com/Parship999) in [#3054](https://github.com/jaegertracing/jaeger-ui/pull/3054))\n\n#### ⚙️ Refactoring\n\n* Convert tracediff component from class to functional component ([@Parship999](https://github.com/Parship999) in [#3099](https://github.com/jaegertracing/jaeger-ui/pull/3099))\n* Remove the history instance from the app component ([@Parship999](https://github.com/Parship999) in [#3100](https://github.com/jaegertracing/jaeger-ui/pull/3100))\n* Update to modern jsx transform ([@Parship999](https://github.com/Parship999) in [#3097](https://github.com/jaegertracing/jaeger-ui/pull/3097))\n* Fix some eslint warnings ([@Parship999](https://github.com/Parship999) in [#3096](https://github.com/jaegertracing/jaeger-ui/pull/3096))\n* Convert servicesview/index to functional component ([@Parship999](https://github.com/Parship999) in [#3004](https://github.com/jaegertracing/jaeger-ui/pull/3004))\n* Convert filteredlist/index.tsx from class to functional component ([@Parship999](https://github.com/Parship999) in [#3083](https://github.com/jaegertracing/jaeger-ui/pull/3083))\n* Fix some lint warnings ([@Parship999](https://github.com/Parship999) in [#3090](https://github.com/jaegertracing/jaeger-ui/pull/3090))\n* Convert searchresults/diffselection to functional component and improved testcases ([@JeevaRamanathan](https://github.com/JeevaRamanathan) in [#3076](https://github.com/jaegertracing/jaeger-ui/pull/3076))\n* Convert tracediff/tracediffheader {cohorttable, tracediffheader} to functional component ([@JeevaRamanathan](https://github.com/JeevaRamanathan) in [#3082](https://github.com/jaegertracing/jaeger-ui/pull/3082))\n* Convert seachresults{resultitem, resultitemtitle} to functional components ([@JeevaRamanathan](https://github.com/JeevaRamanathan) in [#3071](https://github.com/jaegertracing/jaeger-ui/pull/3071))\n* Tighten tracearchive type to more strictly enforce correct state ([@tklever](https://github.com/tklever) in [#623](https://github.com/jaegertracing/jaeger-ui/pull/623))\n\n\n\nv1.73.0 / v2.10.0 (2025-09-02)\n-------------------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Chore(jaeger-tracegen): upgrade tracegen docker compose to jaeger-v2 ([@lekaf974](https://github.com/lekaf974) in [#7481](https://github.com/jaegertracing/jaeger/pull/7481))\n* Fix extra `_total` suffix in metrics ([@pipiland2612](https://github.com/pipiland2612) in [#7476](https://github.com/jaegertracing/jaeger/pull/7476))\n\n#### 🚧 Experimental Features\n\n* Add timeout to helm commands in jaeger demo deployment ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7488](https://github.com/jaegertracing/jaeger/pull/7488))\n* Separate scripts for deployment upgrade and clean install of jaeger demo deployment and enable g-tracking in jaeger ui ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7440](https://github.com/jaegertracing/jaeger/pull/7440))\n* Redirect to demo documentation in jaeger demo deployment ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7429](https://github.com/jaegertracing/jaeger/pull/7429))\n* Multiple minor changes in jaeger demo deployment in oke ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7427](https://github.com/jaegertracing/jaeger/pull/7427))\n* Change example hotrod version in jaeger demo version ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7425](https://github.com/jaegertracing/jaeger/pull/7425))\n\n#### 👷 CI Improvements\n\n* Validate jaeger demo configurations in ci workflow ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7464](https://github.com/jaegertracing/jaeger/pull/7464))\n* [ci] sanitize transient metric labels before comparison ([@wololowarrior](https://github.com/wololowarrior) in [#7482](https://github.com/jaegertracing/jaeger/pull/7482))\n* Download pre-built go tip from grafana/gotip repo ([@yurishkuro](https://github.com/yurishkuro) in [#7447](https://github.com/jaegertracing/jaeger/pull/7447))\n* [ci] improve summary comment ([@pipiland2612](https://github.com/pipiland2612) in [#7462](https://github.com/jaegertracing/jaeger/pull/7462))\n* [ci] improve on metrics comment ([@pipiland2612](https://github.com/pipiland2612) in [#7449](https://github.com/jaegertracing/jaeger/pull/7449))\n* [ci] add upload pr_number artifacts action to ci-e2e-all.yml ([@pipiland2612](https://github.com/pipiland2612) in [#7448](https://github.com/jaegertracing/jaeger/pull/7448))\n* [ci] add new post comment workflow ([@pipiland2612](https://github.com/pipiland2612) in [#7414](https://github.com/jaegertracing/jaeger/pull/7414))\n* Use latest go version when building gotip ([@yurishkuro](https://github.com/yurishkuro) in [#7445](https://github.com/jaegertracing/jaeger/pull/7445))\n\n#### ⚙️ Refactoring\n\n* Chore: enable badlock from go-critic ([@mmorel-35](https://github.com/mmorel-35) in [#7437](https://github.com/jaegertracing/jaeger/pull/7437))\n* Chore: enable rangevalcopy from go-critic ([@mmorel-35](https://github.com/mmorel-35) in [#7438](https://github.com/jaegertracing/jaeger/pull/7438))\n* Chore: enable more rules from go-critic ([@mmorel-35](https://github.com/mmorel-35) in [#7434](https://github.com/jaegertracing/jaeger/pull/7434))\n* Chore: enable several rules from go-critic ([@mmorel-35](https://github.com/mmorel-35) in [#7430](https://github.com/jaegertracing/jaeger/pull/7430))\n* Chore: enable several rules from staticcheck by default ([@mmorel-35](https://github.com/mmorel-35) in [#7431](https://github.com/jaegertracing/jaeger/pull/7431))\n\n\n### 📊 UI Changes\n\n#### ✨ New Features\n\n* Upgrade project to react 19 ([@vishvamsinh28](https://github.com/vishvamsinh28) in [#3040](https://github.com/jaegertracing/jaeger-ui/pull/3040))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Make the scrollbar always visible in lookback dropdown ([@Parship999](https://github.com/Parship999) in [#3048](https://github.com/jaegertracing/jaeger-ui/pull/3048))\n* Add click to copy for trace id ([@Darshit42](https://github.com/Darshit42) in [#2997](https://github.com/jaegertracing/jaeger-ui/pull/2997))\n* Improve performance on trace statistics page after value for sub-group is selected ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2843](https://github.com/jaegertracing/jaeger-ui/pull/2843))\n* Highlight active mode button in tracegraph ([@Saquib45](https://github.com/Saquib45) in [#3034](https://github.com/jaegertracing/jaeger-ui/pull/3034))\n\n#### ⚙️ Refactoring\n\n* Fix typescript ref typing errors across react components ([@vishvamsinh28](https://github.com/vishvamsinh28) in [#3042](https://github.com/jaegertracing/jaeger-ui/pull/3042))\n* Convert `VerticalResizer.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2951](https://github.com/jaegertracing/jaeger-ui/pull/2951))\n\n\nv1.72.0 / v2.9.0 (2025-08-03)\n-------------------------------\n### Backend Changes\n\n#### ✨ New Features\n\n* Implement custom rangequery interface to support elasticsearch v9 ([@shuraih775](https://github.com/shuraih775) in [#7358](https://github.com/jaegertracing/jaeger/pull/7358))\n* Add opensearch 3.x support ([@Parship999](https://github.com/Parship999) in [#7356](https://github.com/jaegertracing/jaeger/pull/7356))\n* Ingress service for jaeger demo deployment ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7308](https://github.com/jaegertracing/jaeger/pull/7308))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Add `/deep-dependencies` endpoint ([@sujalshah-bit](https://github.com/sujalshah-bit) in [#7399](https://github.com/jaegertracing/jaeger/pull/7399))\n* Add api key authentication support for elasticsearch storage ([@danish9039](https://github.com/danish9039) in [#7402](https://github.com/jaegertracing/jaeger/pull/7402))\n* Fix kafka tls configuration with plaintext authentication ([@Parship999](https://github.com/Parship999) in [#7395](https://github.com/jaegertracing/jaeger/pull/7395))\n* Add alerts for jaeger 2.x ([@danish9039](https://github.com/danish9039) in [#6854](https://github.com/jaegertracing/jaeger/pull/6854))\n* Add missing mapstructure tag for tls in promcfg/config.go ([@pipiland2612](https://github.com/pipiland2612) in [#7367](https://github.com/jaegertracing/jaeger/pull/7367))\n* Add bearer token reloading and reuse in multiple storage backends ([@danish9039](https://github.com/danish9039) in [#7360](https://github.com/jaegertracing/jaeger/pull/7360))\n* Fixed invalid string type issue for array-valued tags ([@Parship999](https://github.com/Parship999) in [#7350](https://github.com/jaegertracing/jaeger/pull/7350))\n* Enable stale bot ([@Parship999](https://github.com/Parship999) in [#7355](https://github.com/jaegertracing/jaeger/pull/7355))\n* Enable automated closing of stale pull requests and issues ([@Parship999](https://github.com/Parship999) in [#7347](https://github.com/jaegertracing/jaeger/pull/7347))\n* Fix codeql security alert: remove sensitive file paths from log messages ([@danish9039](https://github.com/danish9039) in [#7345](https://github.com/jaegertracing/jaeger/pull/7345))\n* Decouple from otel collector semconv package ([@danish9039](https://github.com/danish9039) in [#7318](https://github.com/jaegertracing/jaeger/pull/7318))\n* Expose jaeger 4318 otlp http port in grafana integration example ([@gokulvootla](https://github.com/gokulvootla) in [#7325](https://github.com/jaegertracing/jaeger/pull/7325))\n* Add ttl to badger sample config ([@yurishkuro](https://github.com/yurishkuro) in [#7319](https://github.com/jaegertracing/jaeger/pull/7319))\n* [hotrod] load jquery from cdn to allow hotrod to work with a basepath ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7321](https://github.com/jaegertracing/jaeger/pull/7321))\n* [refactor] improved  gethttproundtripper ([@danish9039](https://github.com/danish9039) in [#7313](https://github.com/jaegertracing/jaeger/pull/7313))\n* Correct command in docker-compose/monitor/readme.md ([@pipiland2612](https://github.com/pipiland2612) in [#7309](https://github.com/jaegertracing/jaeger/pull/7309))\n\n#### 🚧 Experimental Features\n\n* Added tls/ssl certification and automation for jaeger demo deployment ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7419](https://github.com/jaegertracing/jaeger/pull/7419))\n* Automate jaeger demo deployment to oke using github actions ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7334](https://github.com/jaegertracing/jaeger/pull/7334))\n* Add required helm repositories for jaeger demo deployment ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7403](https://github.com/jaegertracing/jaeger/pull/7403))\n* Add metrics_storage to config-elasticsearch/opensearch ([@pipiland2612](https://github.com/pipiland2612) in [#7390](https://github.com/jaegertracing/jaeger/pull/7390))\n* Change basepath and remove unused yaml for jaeger demo deployment ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7374](https://github.com/jaegertracing/jaeger/pull/7374))\n* Add readiness and liveness probe paths to demo deployment for improved health checks ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7371](https://github.com/jaegertracing/jaeger/pull/7371))\n* [spm] add optimisation by time range ([@pipiland2612](https://github.com/pipiland2612) in [#7322](https://github.com/jaegertracing/jaeger/pull/7322))\n* Serve hotrod ui and grafana from separate basepaths in jaeger demo ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7328](https://github.com/jaegertracing/jaeger/pull/7328))\n* Add support for elasticsearch v9 index template creation by reusing v8 template ([@shuraih775](https://github.com/shuraih775) in [#7320](https://github.com/jaegertracing/jaeger/pull/7320))\n* [spm] add opensearch option ([@pipiland2612](https://github.com/pipiland2612) in [#7304](https://github.com/jaegertracing/jaeger/pull/7304))\n* [spm] bug fix for metricstore/elasticsearch/processor.go ([@pipiland2612](https://github.com/pipiland2612) in [#7303](https://github.com/jaegertracing/jaeger/pull/7303))\n* Add jaeger demo monitoring setup with deployment script ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7300](https://github.com/jaegertracing/jaeger/pull/7300))\n* [spm] geterrorrates implementation ([@pipiland2612](https://github.com/pipiland2612) in [#7298](https://github.com/jaegertracing/jaeger/pull/7298))\n* [spm] getlatencies implementation ([@pipiland2612](https://github.com/pipiland2612) in [#7290](https://github.com/jaegertracing/jaeger/pull/7290))\n* Add load generator for jaeger demo to generate trace data from hotrod service ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7296](https://github.com/jaegertracing/jaeger/pull/7296))\n* Refactor metricstore/elasticsearch/reader.go ([@pipiland2612](https://github.com/pipiland2612) in [#7295](https://github.com/jaegertracing/jaeger/pull/7295))\n\n#### 👷 CI Improvements\n\n* [ci] add metrics summary action ([@pipiland2612](https://github.com/pipiland2612) in [#7376](https://github.com/jaegertracing/jaeger/pull/7376))\n* [spm e2e] add e2e test for new spm (using elasticsearch as metrics_backend) ([@pipiland2612](https://github.com/pipiland2612) in [#7307](https://github.com/jaegertracing/jaeger/pull/7307))\n\n#### ⚙️ Refactoring\n\n* Refactor basic authentication to http transport layer ([@danish9039](https://github.com/danish9039) in [#7388](https://github.com/jaegertracing/jaeger/pull/7388))\n* Fix enforce-switch-style linting errors ([@Andrei-hub11](https://github.com/Andrei-hub11) in [#7387](https://github.com/jaegertracing/jaeger/pull/7387))\n* Remove pool.go/pool.stop method (deadcode) ([@Parship999](https://github.com/Parship999) in [#7373](https://github.com/jaegertracing/jaeger/pull/7373))\n* Removing dead code from thrift/jaeger ([@wololowarrior](https://github.com/wololowarrior) in [#7365](https://github.com/jaegertracing/jaeger/pull/7365))\n* Remove sanitizer/service_name_sanitizer (dead code) ([@Parship999](https://github.com/Parship999) in [#7366](https://github.com/jaegertracing/jaeger/pull/7366))\n* Remove deadcode jptrace/utf8 ([@Parship999](https://github.com/Parship999) in [#7369](https://github.com/jaegertracing/jaeger/pull/7369))\n* Removal of all-in-one/setupcontext package (dead codes) ([@Parship999](https://github.com/Parship999) in [#7359](https://github.com/jaegertracing/jaeger/pull/7359))\n* Remove of sanitizer/utf8_sanitizer (dead code) ([@Parship999](https://github.com/Parship999) in [#7363](https://github.com/jaegertracing/jaeger/pull/7363))\n* Removal of sanitizer/cache package (dead code) ([@Parship999](https://github.com/Parship999) in [#7357](https://github.com/jaegertracing/jaeger/pull/7357))\n* [refactor] used otel optional type for union auth struct ([@danish9039](https://github.com/danish9039) in [#7316](https://github.com/jaegertracing/jaeger/pull/7316))\n* [refactor] move bearertoken under auth/ ([@danish9039](https://github.com/danish9039) in [#7312](https://github.com/jaegertracing/jaeger/pull/7312))\n* [badger] give responsibility of creating v2 factory to storage backend ([@Manik2708](https://github.com/Manik2708) in [#7299](https://github.com/jaegertracing/jaeger/pull/7299))\n* Refactor metricstore/es/reader.go by introducing querybuilder ([@pipiland2612](https://github.com/pipiland2612) in [#7297](https://github.com/jaegertracing/jaeger/pull/7297))\n\n\nv1.71.0 / v2.8.0 (2025-07-03)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [es] materialize span.kind and span.status tags ([@pipiland2612](https://github.com/pipiland2612) in [#7272](https://github.com/jaegertracing/jaeger/pull/7272))\n* Make jaeger.es.disablelegacyid feature stable ([@yurishkuro](https://github.com/yurishkuro) in [#7267](https://github.com/jaegertracing/jaeger/pull/7267))\n\n#### ✨ New Features\n\n* [v2] switch memory backend to storage api v2 implementation ([@Manik2708](https://github.com/Manik2708) in [#7157](https://github.com/jaegertracing/jaeger/pull/7157))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix panic when reading malformed warning attribute ([@yurishkuro](https://github.com/yurishkuro) in [#7293](https://github.com/jaegertracing/jaeger/pull/7293))\n* [fix] prevent panic when sanitizing read-only traces with multiple exporters ([@victornguen](https://github.com/victornguen) in [#7245](https://github.com/jaegertracing/jaeger/pull/7245))\n* Update elasticsearch to use olivere/elastic/v7 ([@pipiland2612](https://github.com/pipiland2612) in [#7244](https://github.com/jaegertracing/jaeger/pull/7244))\n* Repoint docker compose files to use cr.jaegertracing.io ([@jkowall](https://github.com/jkowall) in [#7240](https://github.com/jaegertracing/jaeger/pull/7240))\n* [es/v2] add metrics decorator for trace reader ([@Manik2708](https://github.com/Manik2708) in [#7201](https://github.com/jaegertracing/jaeger/pull/7201))\n\n#### 🚧 Experimental Features\n\n* [spm] getcallrate implementation ([@pipiland2612](https://github.com/pipiland2612) in [#7229](https://github.com/jaegertracing/jaeger/pull/7229))\n* [cassandra] give responsibility of creating v2 factory to storage backend ([@Manik2708](https://github.com/Manik2708) in [#7228](https://github.com/jaegertracing/jaeger/pull/7228))\n* Jaeger demo on kubernetes ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#7262](https://github.com/jaegertracing/jaeger/pull/7262))\n* [clickhouse] implement gettraces for clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7207](https://github.com/jaegertracing/jaeger/pull/7207))\n* [spm] and esclient to es/reader.go and refactor test ([@pipiland2612](https://github.com/pipiland2612) in [#7216](https://github.com/jaegertracing/jaeger/pull/7216))\n* Add skeleton implementation for es as metricstorage for spm ([@pipiland2612](https://github.com/pipiland2612) in [#7209](https://github.com/jaegertracing/jaeger/pull/7209))\n\n### 📊 UI Changes\n\n#### ⚙️ Refactoring\n\n* Convert `opsgraph.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2914](https://github.com/jaegertracing/jaeger-ui/pull/2914))\n* Convert `regiondemo.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2910](https://github.com/jaegertracing/jaeger-ui/pull/2910))\n* Convert `dividerdemo.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2909](https://github.com/jaegertracing/jaeger-ui/pull/2909))\n* Convert `draggablemanagerdemo.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2908](https://github.com/jaegertracing/jaeger-ui/pull/2908))\n* Convert `nameselector.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2889](https://github.com/jaegertracing/jaeger-ui/pull/2889))\n* Convert `copyicon.tsx` from class component to functional component ([@Parship999](https://github.com/Parship999) in [#2887](https://github.com/jaegertracing/jaeger-ui/pull/2887))\n\n</details>\n\nv1.70.0 / v2.7.0 (2025-06-10)\n-------------------------------\n\n### Backend Changes\n\n#### ✨ New Features\n\n* [feat] use v2 es/os storage in jaeger-v2 ([@Manik2708](https://github.com/Manik2708) in [#7151](https://github.com/jaegertracing/jaeger/pull/7151))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Feat: add option to disable elasticsearch health check ([@timonegk](https://github.com/timonegk) in [#7212](https://github.com/jaegertracing/jaeger/pull/7212))\n* Fix(elasticsearch): respect explicitly configured replicas=0 in index… ([@masihkhatibzadeh99](https://github.com/masihkhatibzadeh99) in [#7160](https://github.com/jaegertracing/jaeger/pull/7160))\n* Add sanitizers for negative span duration ([@iypetrov](https://github.com/iypetrov) in [#7122](https://github.com/jaegertracing/jaeger/pull/7122))\n* [fix] fix prometheus label value is not valid utf 8 cause api timeout ([@iypetrov](https://github.com/iypetrov) in [#7128](https://github.com/jaegertracing/jaeger/pull/7128))\n* Add retries to ilm client ([@iypetrov](https://github.com/iypetrov) in [#7120](https://github.com/jaegertracing/jaeger/pull/7120))\n* Add retry configuration to storage exporter ([@kumarlokesh](https://github.com/kumarlokesh) in [#7132](https://github.com/jaegertracing/jaeger/pull/7132))\n* [fix] restore es metrics ([@AnmolxSingh](https://github.com/AnmolxSingh) in [#7006](https://github.com/jaegertracing/jaeger/pull/7006))\n* [fix] propagate environment variables to binary from integration tests ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7112](https://github.com/jaegertracing/jaeger/pull/7112))\n\n#### 🚧 Experimental Features\n\n* [refactor] rework clickhouse schema structure ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7181](https://github.com/jaegertracing/jaeger/pull/7181))\n* [clickhouse] implement getoperations for trace reader in clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7180](https://github.com/jaegertracing/jaeger/pull/7180))\n* [clickhouse] implement getservices for trace reader in clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7159](https://github.com/jaegertracing/jaeger/pull/7159))\n* [v2] implement `getdependencies` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7154](https://github.com/jaegertracing/jaeger/pull/7154))\n* [v2] implement `gettraces` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7152](https://github.com/jaegertracing/jaeger/pull/7152))\n* [v2] implement `findtraceids` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7143](https://github.com/jaegertracing/jaeger/pull/7143))\n* Add description for docker-compose-elasticsearch.yml ([@pipiland2612](https://github.com/pipiland2612) in [#7146](https://github.com/jaegertracing/jaeger/pull/7146))\n* Add e2e test for docker-compose-elasticsearch.yml file ([@pipiland2612](https://github.com/pipiland2612) in [#7145](https://github.com/jaegertracing/jaeger/pull/7145))\n* Add docker-compose-elasticsearch.yml and its sample configuration ([@pipiland2612](https://github.com/pipiland2612) in [#7144](https://github.com/jaegertracing/jaeger/pull/7144))\n* [v2] implement `findtraces` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7062](https://github.com/jaegertracing/jaeger/pull/7062))\n\n#### ⚙️ Refactoring\n\n* Relocate the docker directory from the root directory to under scripts/build ([@ris-tlp](https://github.com/ris-tlp) in [#7189](https://github.com/jaegertracing/jaeger/pull/7189))\n* [refactor] move sanitizer ([@yurishkuro](https://github.com/yurishkuro) in [#7158](https://github.com/jaegertracing/jaeger/pull/7158))\n* [es][v2] refactor the factory of v1 to make it reusable for v2 ([@Manik2708](https://github.com/Manik2708) in [#7086](https://github.com/jaegertracing/jaeger/pull/7086))\n* [refactor] allow storage cleaner to be overridden via environment variable ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7114](https://github.com/jaegertracing/jaeger/pull/7114))\n* [refactor] remove archive storage from grpc config ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7113](https://github.com/jaegertracing/jaeger/pull/7113))\n* [grpc] allow remote storage endpoint to be set via environment variable ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7111](https://github.com/jaegertracing/jaeger/pull/7111))\n* [refactor] use proto files from `jaeger-idl` for remote storage api ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7104](https://github.com/jaegertracing/jaeger/pull/7104))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix react fragment key issues in multiple components ([@Parship999](https://github.com/Parship999) in [#2823](https://github.com/jaegertracing/jaeger-ui/pull/2823))\n* Move tracediff header chevron icon ([@Parship999](https://github.com/Parship999) in [#2845](https://github.com/jaegertracing/jaeger-ui/pull/2845))\n* Feat: filter logs based on the selected time range ([@tejas-raskar](https://github.com/tejas-raskar) in [#2844](https://github.com/jaegertracing/jaeger-ui/pull/2844))\n* Enhance tracediff ui components ([@Parship999](https://github.com/Parship999) in [#2806](https://github.com/jaegertracing/jaeger-ui/pull/2806))\n* Rewrite computeselftime to improve performance on trace statistics page ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2767](https://github.com/jaegertracing/jaeger-ui/pull/2767))\n* Fix array return pattern in `labeledlist` component ([@Parship999](https://github.com/Parship999) in [#2812](https://github.com/jaegertracing/jaeger-ui/pull/2812))\n* Allow json logs to occupy entire available width ([@tejas-raskar](https://github.com/tejas-raskar) in [#2814](https://github.com/jaegertracing/jaeger-ui/pull/2814))\n* Feat: convert monitoratmemptystate to a functional component ([@vishvamsinh28](https://github.com/vishvamsinh28) in [#2790](https://github.com/jaegertracing/jaeger-ui/pull/2790))\n* Replace deprecated `overlayclassname` with `classnames.root` ([@abhayporwals](https://github.com/abhayporwals) in [#2772](https://github.com/jaegertracing/jaeger-ui/pull/2772))\n* [fix]: reduce default minimum allowed zoom ([@hari45678](https://github.com/hari45678) in [#2775](https://github.com/jaegertracing/jaeger-ui/pull/2775))\n* Fix dependencygraph dag extra render ([@mdwyer6](https://github.com/mdwyer6) in [#2749](https://github.com/jaegertracing/jaeger-ui/pull/2749))\n\n#### ⚙️ Refactoring\n\n* Convert qualitymetrics components to functional components ([@Parship999](https://github.com/Parship999) in [#2856](https://github.com/jaegertracing/jaeger-ui/pull/2856))\n* Refactor: spandetailrow to functional component ([@tejas-raskar](https://github.com/tejas-raskar) in [#2827](https://github.com/jaegertracing/jaeger-ui/pull/2827))\n* Migrate tracetimelineviewerimpl to a functional component ([@tejas-raskar](https://github.com/tejas-raskar) in [#2816](https://github.com/jaegertracing/jaeger-ui/pull/2816))\n* Refactor canvasspangraph to functional component and improve test coverage ([@vishvamsinh28](https://github.com/vishvamsinh28) in [#2824](https://github.com/jaegertracing/jaeger-ui/pull/2824))\n\nv1.69.0 / v2.6.0 (2025-05-08)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Feat(elasticsearch): Add flag to enable gzip compression by default ([@timonegk](https://github.com/timonegk) in [#7072](https://github.com/jaegertracing/jaeger/pull/7072))\n* Only Remote Storage API v2 is supported in Jaeger v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6969](https://github.com/jaegertracing/jaeger/pull/6969))\n\n\n#### ✨ New Features\n\n* Add filterprocessor ([@yurishkuro](https://github.com/yurishkuro) in [#7094](https://github.com/jaegertracing/jaeger/pull/7094))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Upgrade reverse-proxy example to jaeger-v2 ([@yurishkuro](https://github.com/yurishkuro) in [#7076](https://github.com/jaegertracing/jaeger/pull/7076))\n* Add pprof extension ([@denysvitali](https://github.com/denysvitali) in [#7073](https://github.com/jaegertracing/jaeger/pull/7073))\n* [es][v1] change the db tag value from `string` to `any` type ([@Manik2708](https://github.com/Manik2708) in [#6998](https://github.com/jaegertracing/jaeger/pull/6998))\n* Remove outdated info related to jaeger exporter ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#6987](https://github.com/jaegertracing/jaeger/pull/6987))\n* [bug] fix the version module path in ldflags ([@developer-guy](https://github.com/developer-guy) in [#6990](https://github.com/jaegertracing/jaeger/pull/6990))\n\n#### 🚧 Experimental Features\n\n* [es][v2] implement `getdependenies` and `writedependencies` ([@Manik2708](https://github.com/Manik2708) in [#7085](https://github.com/jaegertracing/jaeger/pull/7085))\n* [clickhouse] convert otel traces model to  native format ([@zhengkezhou1](https://github.com/zhengkezhou1) in [#6935](https://github.com/jaegertracing/jaeger/pull/6935))\n* [es] make `nestedtags` and `elevatedtags` distinction at `corespanreader` level ([@Manik2708](https://github.com/Manik2708) in [#7067](https://github.com/jaegertracing/jaeger/pull/7067))\n* [es][v2] move `coredependencystore` and `dbmodel` from v1 to v2 ([@Manik2708](https://github.com/Manik2708) in [#7079](https://github.com/jaegertracing/jaeger/pull/7079))\n* [grpc][v2] use standard otlp receiver for grpc storage write path ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7065](https://github.com/jaegertracing/jaeger/pull/7065))\n* [es][v2] implement `findtraces` for es/os for v2 ([@Manik2708](https://github.com/Manik2708) in [#7021](https://github.com/jaegertracing/jaeger/pull/7021))\n* [refactor] remove `jaeger_query` extension from remote storage backend config ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7059](https://github.com/jaegertracing/jaeger/pull/7059))\n* [v2][remote-storage] implement remote storage extension ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7043](https://github.com/jaegertracing/jaeger/pull/7043))\n* [es][v2] implement `gettraces` for es/os ([@Manik2708](https://github.com/Manik2708) in [#7054](https://github.com/jaegertracing/jaeger/pull/7054))\n* [v2] implement `getoperations` and `getservices` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7053](https://github.com/jaegertracing/jaeger/pull/7053))\n* [v2] implement `findtraceids` for es/os ([@Manik2708](https://github.com/Manik2708) in [#7035](https://github.com/jaegertracing/jaeger/pull/7035))\n* [v2] implement `writetraces` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7027](https://github.com/jaegertracing/jaeger/pull/7027))\n* [es] refactor `dependencystore` to make it reusable for v2 apis ([@Manik2708](https://github.com/Manik2708) in [#7044](https://github.com/jaegertracing/jaeger/pull/7044))\n* [grpc][v2] use v2 grpc factory in storage extension ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6969](https://github.com/jaegertracing/jaeger/pull/6969))\n* [grpc][v2] register grpc v2 handler in remote-storage server ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7037](https://github.com/jaegertracing/jaeger/pull/7037))\n* [es][v2] implement `getoperations` and `getservices` for v2 ([@Manik2708](https://github.com/Manik2708) in [#7025](https://github.com/jaegertracing/jaeger/pull/7025))\n* [es][v2] implement `writetraces` for v2 ([@Manik2708](https://github.com/Manik2708) in [#7020](https://github.com/jaegertracing/jaeger/pull/7020))\n* [grpc][v2] implement `getdependencies` in grpc v2 server ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7016](https://github.com/jaegertracing/jaeger/pull/7016))\n* [grpc][v2] implement otlp exporter api in grpc v2 handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7012](https://github.com/jaegertracing/jaeger/pull/7012))\n* [es][v2] change the db tag value from `string` to `any` type ([@Manik2708](https://github.com/Manik2708) in [#6994](https://github.com/jaegertracing/jaeger/pull/6994))\n* [grpc][v2] implement findtraceids in grpc v2 handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7003](https://github.com/jaegertracing/jaeger/pull/7003))\n* [grpc][v2] implement `findtraces` in grpc v2 handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6992](https://github.com/jaegertracing/jaeger/pull/6992))\n* [grpc][v2] implement `gettraces` in grpc v2 handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6985](https://github.com/jaegertracing/jaeger/pull/6985))\n* [grpc][v2] implement getservices in grpc v2 handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6984](https://github.com/jaegertracing/jaeger/pull/6984))\n* [grpc][v2] implement `getservices` in grpc v2 handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6980](https://github.com/jaegertracing/jaeger/pull/6980))\n\n#### 👷 CI Improvements\n\n* Do not run binary size check on push to main ([@yurishkuro](https://github.com/yurishkuro) in [#7096](https://github.com/jaegertracing/jaeger/pull/7096))\n* Check that version number is corectly embedded in the binary ([@yurishkuro](https://github.com/yurishkuro) in [#7092](https://github.com/jaegertracing/jaeger/pull/7092))\n* Update module github.com/vektra/mockery/v2 to v3 ([@AnmolxSingh](https://github.com/AnmolxSingh) in [#7051](https://github.com/jaegertracing/jaeger/pull/7051))\n* [fix] add query integration test to workflows file ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7056](https://github.com/jaegertracing/jaeger/pull/7056))\n* Enable mockery/with-expecter ([@yurishkuro](https://github.com/yurishkuro) in [#7046](https://github.com/jaegertracing/jaeger/pull/7046))\n* Fix paths in mockery config ([@yurishkuro](https://github.com/yurishkuro) in [#7045](https://github.com/jaegertracing/jaeger/pull/7045))\n* Fix flakiness in runindexcleanertest by filtering jaeger indices ([@0xShubhamSolanki](https://github.com/0xShubhamSolanki) in [#7004](https://github.com/jaegertracing/jaeger/pull/7004))\n* Add e2e integration test for query service ([@pipiland2612](https://github.com/pipiland2612) in [#6966](https://github.com/jaegertracing/jaeger/pull/6966))\n* #5608 improve spm e2e test with test for error rate ([@pipiland2612](https://github.com/pipiland2612) in [#6991](https://github.com/jaegertracing/jaeger/pull/6991))\n\n#### ⚙️ Refactoring\n\n* [es] make `nestedtags` and `fieldtags` distinction at `corespanwriter` level ([@Manik2708](https://github.com/Manik2708) in [#6946](https://github.com/jaegertracing/jaeger/pull/6946))\n* [refactor] change remote storage server to accept v2 factories ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7024](https://github.com/jaegertracing/jaeger/pull/7024))\n* [refactor] consolidate v1/v2 writer factory adapter functionality ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7022](https://github.com/jaegertracing/jaeger/pull/7022))\n* [refactor] consolidate v1/v2 reader factory adapter functionality ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7019](https://github.com/jaegertracing/jaeger/pull/7019))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Reduce load time of trace page by deferring critical path tooltip ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2718](https://github.com/jaegertracing/jaeger-ui/pull/2718))\n* Migrate copyicon tests ([@nojaf](https://github.com/nojaf) in [#2727](https://github.com/jaegertracing/jaeger-ui/pull/2727))\n* [fix]: make reset icon in sdg more intuitive ([@hari45678](https://github.com/hari45678) in [#2723](https://github.com/jaegertracing/jaeger-ui/pull/2723))\n* Migrate from enzyme to @testing-library/react in keyboardshortshelp ([@nojaf](https://github.com/nojaf) in [#2725](https://github.com/jaegertracing/jaeger-ui/pull/2725))\n* Improve performance of trace statistics page when grouping by tag ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2724](https://github.com/jaegertracing/jaeger-ui/pull/2724))\n* Improve performance of expanding and collapsing spans ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2722](https://github.com/jaegertracing/jaeger-ui/pull/2722))\n* Improve performance of trace statistics ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2721](https://github.com/jaegertracing/jaeger-ui/pull/2721))\n* [feat]: add context menu on node to dag ([@hari45678](https://github.com/hari45678) in [#2719](https://github.com/jaegertracing/jaeger-ui/pull/2719))\n* Fix grouping on trace statistics page for tags ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2717](https://github.com/jaegertracing/jaeger-ui/pull/2717))\n* Improve performance when expanding/collapsing span details ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2716](https://github.com/jaegertracing/jaeger-ui/pull/2716))\n\n#### 👷 CI Improvements\n\n* Add ability to use typescript in tests ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2731](https://github.com/jaegertracing/jaeger-ui/pull/2731))\n\n#### ⚙️ Refactoring\n\n* [es] make `nestedtags` and `fieldtags` distinction at `corespanwriter` level ([@Manik2708](https://github.com/Manik2708) in [#6946](https://github.com/jaegertracing/jaeger/pull/6946))\n* [refactor] change remote storage server to accept v2 factories ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7024](https://github.com/jaegertracing/jaeger/pull/7024))\n* [refactor] consolidate v1/v2 writer factory adapter functionality ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7022](https://github.com/jaegertracing/jaeger/pull/7022))\n* [refactor] consolidate v1/v2 reader factory adapter functionality ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7019](https://github.com/jaegertracing/jaeger/pull/7019))\n\nv1.68.0 / v2.5.0 (2025-04-05)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove sampling.strategies.bugfix-5270 flag and mark feature stable ([@yurishkuro](https://github.com/yurishkuro) in [#6872](https://github.com/jaegertracing/jaeger/pull/6872))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Minor fixes to release checklist generator ([@albertteoh](https://github.com/albertteoh) in [#6976](https://github.com/jaegertracing/jaeger/pull/6976))\n* Support configuring prometheus.extra_query_parameters via cli ([@andreasgerstmayr](https://github.com/andreasgerstmayr) in [#6931](https://github.com/jaegertracing/jaeger/pull/6931))\n* Cleanup legacy models ([@yurishkuro](https://github.com/yurishkuro) in [#6875](https://github.com/jaegertracing/jaeger/pull/6875))\n* 🪦 remove agent code ([@yurishkuro](https://github.com/yurishkuro) in [#6868](https://github.com/jaegertracing/jaeger/pull/6868))\n* [es] refactor `findtraces` and `gettrace` of spanreader to make them reusable for v2 apis ([@Manik2708](https://github.com/Manik2708) in [#6845](https://github.com/jaegertracing/jaeger/pull/6845))\n* [es] add feature to stop legacy trace ids handling in spanreader ([@Manik2708](https://github.com/Manik2708) in [#6848](https://github.com/jaegertracing/jaeger/pull/6848))\n* Feat: move pkg/testutils to internal/testutils ([@jinjiaKarl](https://github.com/jinjiaKarl) in [#6840](https://github.com/jaegertracing/jaeger/pull/6840))\n* [fix] allow es-index-cleaner to delete indices based on current time ([@Asatyam](https://github.com/Asatyam) in [#6790](https://github.com/jaegertracing/jaeger/pull/6790))\n* [chore] remove gogoproto annotations from `trace_storage.proto` and `dependency_storage.proto` ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6819](https://github.com/jaegertracing/jaeger/pull/6819))\n\n#### 🚧 Experimental Features\n\n* [es][v2] add snapshot tests for spans conversion ([@Manik2708](https://github.com/Manik2708) in [#6970](https://github.com/jaegertracing/jaeger/pull/6970))\n* [grpc][v2] implement grpc v2 factory ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6968](https://github.com/jaegertracing/jaeger/pull/6968))\n* [grpc][v2] implement `findtraces` call in grpc reader for remote storage api v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6962](https://github.com/jaegertracing/jaeger/pull/6962))\n* [grpc][v2] implement `gettraces` call in grpc reader for remote storage api v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6857](https://github.com/jaegertracing/jaeger/pull/6857))\n* [es][v2] refactor `from_dbmodel` and `to_dbmodel` to accept and return db spans ([@Manik2708](https://github.com/Manik2708) in [#6934](https://github.com/jaegertracing/jaeger/pull/6934))\n* [grpc][v2] implement v2 grpc dependency reader ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6933](https://github.com/jaegertracing/jaeger/pull/6933))\n* [es][v2] copy jaeger<->otlp translator from otel contrib ([@Manik2708](https://github.com/Manik2708) in [#6923](https://github.com/jaegertracing/jaeger/pull/6923))\n* [grpc][v2] implement v2 grpc trace writer ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6919](https://github.com/jaegertracing/jaeger/pull/6919))\n* [grpc][v2] implement `findtraceids` call in grpc reader for remote storage api v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6858](https://github.com/jaegertracing/jaeger/pull/6858))\n* [grpc][v2] implement `getoperations` call in grpc reader for remote storage api v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6843](https://github.com/jaegertracing/jaeger/pull/6843))\n* [grpc][v2] implement `getservices` call in grpc reader for remote storage api v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6829](https://github.com/jaegertracing/jaeger/pull/6829))\n* [refactor] return chunk of traces from remote storage api v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6830](https://github.com/jaegertracing/jaeger/pull/6830))\n\n#### 👷 CI Improvements\n\n* [all-in-one] avoid multi-arch builds in merge queue (#6880) ([@sAchin-680](https://github.com/sAchin-680) in [#6882](https://github.com/jaegertracing/jaeger/pull/6882))\n* Instruct renovate to pin github action hashes ([@yurishkuro](https://github.com/yurishkuro) in [#6860](https://github.com/jaegertracing/jaeger/pull/6860))\n\n#### ⚙️ Refactoring\n\n* Move model/json/model.go to internal/uimodel/converter/v1 ([@pipiland2612](https://github.com/pipiland2612) in [#6973](https://github.com/jaegertracing/jaeger/pull/6973))\n* Add usetesting linter and fix lint issues (#6892) ([@anurag-rajawat](https://github.com/anurag-rajawat) in [#6972](https://github.com/jaegertracing/jaeger/pull/6972))\n* Delete empty pkg package ([@pipiland2612](https://github.com/pipiland2612) in [#6967](https://github.com/jaegertracing/jaeger/pull/6967))\n* Move pkg/otelsemconv to internal/telemetry/otelsemconv ([@pipiland2612](https://github.com/pipiland2612) in [#6961](https://github.com/jaegertracing/jaeger/pull/6961))\n* Move pkg/cassandra to internal/storage/cassandra ([@pipiland2612](https://github.com/pipiland2612) in [#6960](https://github.com/jaegertracing/jaeger/pull/6960))\n* Move pkg/adjuster to cmd/query/app/querysvc/internal/adjuster ([@pipiland2612](https://github.com/pipiland2612) in [#6956](https://github.com/jaegertracing/jaeger/pull/6956))\n* Remove package pkg/netutils ([@pipiland2612](https://github.com/pipiland2612) in [#6955](https://github.com/jaegertracing/jaeger/pull/6955))\n* [refactor] remove `traceschunk` type and stream otlp traces directly ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6954](https://github.com/jaegertracing/jaeger/pull/6954))\n* [es] remove pointer signatures from `fromdbmodel` and `todbmodel` ([@Manik2708](https://github.com/Manik2708) in [#6942](https://github.com/jaegertracing/jaeger/pull/6942))\n* Move proto-gen to internal ([@yurishkuro](https://github.com/yurishkuro) in [#6941](https://github.com/jaegertracing/jaeger/pull/6941))\n* Move pkg/es to internal/storage/elasticsearch ([@danish9039](https://github.com/danish9039) in [#6937](https://github.com/jaegertracing/jaeger/pull/6937))\n* Move pkg/distributedlock to internal/distributedlock ([@danish9039](https://github.com/danish9039) in [#6903](https://github.com/jaegertracing/jaeger/pull/6903))\n* Move pkg/httpmetrics to internal/httpmetrics ([@danish9039](https://github.com/danish9039) in [#6905](https://github.com/jaegertracing/jaeger/pull/6905))\n* Move pkg/{gogocodec,httpfs,bearertoken,boundqueue} to internal ([@sAchin-680](https://github.com/sAchin-680) in [#6896](https://github.com/jaegertracing/jaeger/pull/6896))\n* Move pkg/metrics to internal/metrics ([@danish9039](https://github.com/danish9039) in [#6901](https://github.com/jaegertracing/jaeger/pull/6901))\n* Move pkg/kafka to internal/kafka ([@danish9039](https://github.com/danish9039) in [#6908](https://github.com/jaegertracing/jaeger/pull/6908))\n* Move pkg/prometheus to internal/config/promcfg ([@danish9039](https://github.com/danish9039) in [#6911](https://github.com/jaegertracing/jaeger/pull/6911))\n* Move model/proto to internal/proto ([@danish9039](https://github.com/danish9039) in [#6918](https://github.com/jaegertracing/jaeger/pull/6918))\n* [es] move db model out of `v1/elasticsearch/internal/spanstore/internal` ([@Manik2708](https://github.com/Manik2708) in [#6894](https://github.com/jaegertracing/jaeger/pull/6894))\n* Move pkg/version to internal/version ([@danish9039](https://github.com/danish9039) in [#6913](https://github.com/jaegertracing/jaeger/pull/6913))\n* Move model/converter to internal/converter ([@danish9039](https://github.com/danish9039) in [#6917](https://github.com/jaegertracing/jaeger/pull/6917))\n* Move pkg/gzipfs to internal/gzipfs ([@sAchin-680](https://github.com/sAchin-680) in [#6897](https://github.com/jaegertracing/jaeger/pull/6897))\n* Move pkg/jtracer to internal/jtracer ([@danish9039](https://github.com/danish9039) in [#6907](https://github.com/jaegertracing/jaeger/pull/6907))\n* Move pkg/telemetry to internal/telemetry ([@danish9039](https://github.com/danish9039) in [#6912](https://github.com/jaegertracing/jaeger/pull/6912))\n* Move pkg/fswatcher to internal/fswatcher ([@sAchin-680](https://github.com/sAchin-680) in [#6895](https://github.com/jaegertracing/jaeger/pull/6895))\n* [es] separate the `corespanwriter` from `spanwriter` ([@Manik2708](https://github.com/Manik2708) in [#6883](https://github.com/jaegertracing/jaeger/pull/6883))\n* Move pkg/config to internal/config ([@gentcod](https://github.com/gentcod) in [#6884](https://github.com/jaegertracing/jaeger/pull/6884))\n* Move pkg/healthcheck to internal/healthcheck ([@danish9039](https://github.com/danish9039) in [#6888](https://github.com/jaegertracing/jaeger/pull/6888))\n* Moved pkg/hostname to internal/hostname ([@danish9039](https://github.com/danish9039) in [#6886](https://github.com/jaegertracing/jaeger/pull/6886))\n* Move pkg/recoveryhandler to internal/recoveryhandler ([@danish9039](https://github.com/danish9039) in [#6887](https://github.com/jaegertracing/jaeger/pull/6887))\n* Move pkg/tenancy to internal/tenancy ([@danish9039](https://github.com/danish9039) in [#6889](https://github.com/jaegertracing/jaeger/pull/6889))\n* Move pkg/normalizer to collector ([@danish9039](https://github.com/danish9039) in [#6877](https://github.com/jaegertracing/jaeger/pull/6877))\n* Replace the use of model/converter/thrift/zipkin ([@shuraih775](https://github.com/shuraih775) in [#6879](https://github.com/jaegertracing/jaeger/pull/6879))\n* [es] remove pointer signatures from `corespanreader` ([@Manik2708](https://github.com/Manik2708) in [#6874](https://github.com/jaegertracing/jaeger/pull/6874))\n* [refactor] move interface to remove cmd/agent dependency ([@yurishkuro](https://github.com/yurishkuro) in [#6863](https://github.com/jaegertracing/jaeger/pull/6863))\n* [agent] refactor udp server ([@yurishkuro](https://github.com/yurishkuro) in [#6852](https://github.com/jaegertracing/jaeger/pull/6852))\n* [agent] remove unnecessary server interface ([@yurishkuro](https://github.com/yurishkuro) in [#6851](https://github.com/jaegertracing/jaeger/pull/6851))\n* [es] refactor the `findtraceids` of spanreader to make them reusable for v2 apis ([@Manik2708](https://github.com/Manik2708) in [#6831](https://github.com/jaegertracing/jaeger/pull/6831))\n* [es] refactor the `getoperations` and `getservices` of spanreader to make them reusable for v2 apis ([@Manik2708](https://github.com/Manik2708) in [#6828](https://github.com/jaegertracing/jaeger/pull/6828))\n\n\n### 📊 UI Changes\n\n#### ✨ New Features\n\n* Feat: change dag styling and add search functionality ([@hari45678](https://github.com/hari45678) in [#2710](https://github.com/jaegertracing/jaeger-ui/pull/2710))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Add sample graph data when in dev mode ([@hari45678](https://github.com/hari45678) in [#2698](https://github.com/jaegertracing/jaeger-ui/pull/2698))\n* Add depth and layout controls and sfdp layout to dag view ([@hari45678](https://github.com/hari45678) in [#2691](https://github.com/jaegertracing/jaeger-ui/pull/2691))\n* Add sfdp engine in @jaegertracing/plexus ([@hari45678](https://github.com/hari45678) in [#2690](https://github.com/jaegertracing/jaeger-ui/pull/2690))\n* Add handling or error for invalid json formats and tests ([@rohitlohar45](https://github.com/rohitlohar45) in [#2689](https://github.com/jaegertracing/jaeger-ui/pull/2689))\n\n\nv1.67.0 / v2.4.0 (2025-03-07)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [query] drop support for shared grpc/http query server ports ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6695](https://github.com/jaegertracing/jaeger/pull/6695))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [es] refactor the es spanwriter to make it reusable for v2 apis ([@Manik2708](https://github.com/Manik2708) in [#6796](https://github.com/jaegertracing/jaeger/pull/6796))\n* [refactor] move internal `tracesdata` type to package `jptrace` ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6809](https://github.com/jaegertracing/jaeger/pull/6809))\n* Use empty slices instead of nil ([@zhengkezhou1](https://github.com/zhengkezhou1) in [#6799](https://github.com/jaegertracing/jaeger/pull/6799))\n* [refactor] refactor `jptrace/attributes_tests.go` for readability ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6786](https://github.com/jaegertracing/jaeger/pull/6786))\n* [refactor] converge v2 api with v2 remote storage api ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6784](https://github.com/jaegertracing/jaeger/pull/6784))\n* Feat: enable configuration of hostnames for hotrod services ([@w-h-a](https://github.com/w-h-a) in [#6782](https://github.com/jaegertracing/jaeger/pull/6782))\n* [refactor] change `tracequeryparams` to accept typed attributes ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6780](https://github.com/jaegertracing/jaeger/pull/6780))\n* [refactor] decouple `tracequeryparams` from `query` in integration tests ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6779](https://github.com/jaegertracing/jaeger/pull/6779))\n* [refactor] inline proto definiton of `keyvalue` from otel ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6775](https://github.com/jaegertracing/jaeger/pull/6775))\n* [refactor] return start and end timestamps from findtraceids in v2 remote storage api ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6772](https://github.com/jaegertracing/jaeger/pull/6772))\n* [refactor] return start and end timestamps from `findtraceids` in v2 api ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6770](https://github.com/jaegertracing/jaeger/pull/6770))\n* Revert \"add 'features' command to print available feature gates\" ([@yurishkuro](https://github.com/yurishkuro) in [#6771](https://github.com/jaegertracing/jaeger/pull/6771))\n* [remote-storage][v2] add complete idl for trace storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6737](https://github.com/jaegertracing/jaeger/pull/6737))\n* [remote-storage][v2] add idl for dependency storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6738](https://github.com/jaegertracing/jaeger/pull/6738))\n* [remote-storage][v2] add proto definition for `getservices` and `getoperations` rpc ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6736](https://github.com/jaegertracing/jaeger/pull/6736))\n* Fix /qualitymetrics to return data in expected format ([@yurishkuro](https://github.com/yurishkuro) in [#6733](https://github.com/jaegertracing/jaeger/pull/6733))\n* [remote-storage][v2] add proto definition for `gettraces` rpc ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6730](https://github.com/jaegertracing/jaeger/pull/6730))\n* [bug][storage] make es-rollover idempotent by checking if the index or alias already exist ([@Manik2708](https://github.com/Manik2708) in [#6638](https://github.com/jaegertracing/jaeger/pull/6638))\n* [refactor] use plain loops with iterators ([@yurishkuro](https://github.com/yurishkuro) in [#6722](https://github.com/jaegertracing/jaeger/pull/6722))\n* Use stdlib iterators ([@yurishkuro](https://github.com/yurishkuro) in [#6714](https://github.com/jaegertracing/jaeger/pull/6714))\n* Create a /quality-metrics endpoint ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#6608](https://github.com/jaegertracing/jaeger/pull/6608))\n* Move pkg/cache to internal ([@won-js](https://github.com/won-js) in [#6720](https://github.com/jaegertracing/jaeger/pull/6720))\n* [storage] change storage extension to hold v2 factories ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6699](https://github.com/jaegertracing/jaeger/pull/6699))\n* Fix go alpine version to 1.24.0 ([@yurishkuro](https://github.com/yurishkuro) in [#6713](https://github.com/jaegertracing/jaeger/pull/6713))\n* [refactor] conditionally implement interfaces in v1adapter factory ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6710](https://github.com/jaegertracing/jaeger/pull/6710))\n* [fix] revert changes to tracereader adapter ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6705](https://github.com/jaegertracing/jaeger/pull/6705))\n* [refactor] conditionally implement interfaces in `v1adapter` ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6701](https://github.com/jaegertracing/jaeger/pull/6701))\n* [refactor] use `gettracestorefactory` instead of `getstoragefactory` ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6696](https://github.com/jaegertracing/jaeger/pull/6696))\n* [storage] add helper to storage extension for retrieving purger ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6694](https://github.com/jaegertracing/jaeger/pull/6694))\n* Import nop receiver/exporter and add a sample query service config ([@danish9039](https://github.com/danish9039) in [#6687](https://github.com/jaegertracing/jaeger/pull/6687))\n* [storage] add helper to storage extension for retrieving sampling store factory ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6689](https://github.com/jaegertracing/jaeger/pull/6689))\n\n#### 👷 CI Improvements\n\n* [idl check] fetch tags ([@yurishkuro](https://github.com/yurishkuro) in [#6758](https://github.com/jaegertracing/jaeger/pull/6758))\n* [test]: check for jaeger-idl version mismatch ([@ary82](https://github.com/ary82) in [#6753](https://github.com/jaegertracing/jaeger/pull/6753))\n* Allow dependency-review workflow to run from merge queue ([@yurishkuro](https://github.com/yurishkuro) in [#6729](https://github.com/jaegertracing/jaeger/pull/6729))\n* Do not run dco-check from merge queue ([@yurishkuro](https://github.com/yurishkuro) in [#6727](https://github.com/jaegertracing/jaeger/pull/6727))\n* Do not run label check from merge queue ([@yurishkuro](https://github.com/yurishkuro) in [#6726](https://github.com/jaegertracing/jaeger/pull/6726))\n* Allow ci workflows to run from merge queue ([@danish9039](https://github.com/danish9039) in [#6719](https://github.com/jaegertracing/jaeger/pull/6719))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Replace react-vis with recharts ([@hari45678](https://github.com/hari45678) in [#2679](https://github.com/jaegertracing/jaeger-ui/pull/2679))\n* Add config option to allow displaying full traceid ([@avinpy-255](https://github.com/avinpy-255) in [#2536](https://github.com/jaegertracing/jaeger-ui/pull/2536))\n\n\n\nv1.66.0 / v2.3.0 (2025-02-03)\n-------------------------------\n#### ⛔ Breaking Changes\n\n* [refactor] remove archive reader and writer from remote storage grpc handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6611](https://github.com/jaegertracing/jaeger/pull/6611))\n* Delete grpc metricsqueryservice, metricsquery.proto and related code ([@yurishkuro](https://github.com/yurishkuro) in [#6616](https://github.com/jaegertracing/jaeger/pull/6616))\n* [storage] remove distinction between primary and `archive` storage interfaces ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6567](https://github.com/jaegertracing/jaeger/pull/6567))\n* [v2][query] create archive reader/writer using regular factory methods ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6519](https://github.com/jaegertracing/jaeger/pull/6519))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [fix] replace deprecated address field in service::telemetry ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6679](https://github.com/jaegertracing/jaeger/pull/6679))\n* [fix] change metrics port in kafka ingester config to avoid conflict with collector ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6678](https://github.com/jaegertracing/jaeger/pull/6678))\n* Update elasticsearch article link ([@timyip3](https://github.com/timyip3) in [#6662](https://github.com/jaegertracing/jaeger/pull/6662))\n* [chore] move scylladb implementation to `docker-compose` ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6652](https://github.com/jaegertracing/jaeger/pull/6652))\n* [fix] refactor archive storage initialization and remove error log ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6636](https://github.com/jaegertracing/jaeger/pull/6636))\n* Update import paths for jaeger thrift files to use jaeger-idl ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6635](https://github.com/jaegertracing/jaeger/pull/6635))\n* [v2][query] apply \"max clock skew adjust\" setting ([@dnaka91](https://github.com/dnaka91) in [#6566](https://github.com/jaegertracing/jaeger/pull/6566))\n* Alias samping.thrift and clean thrift files ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6630](https://github.com/jaegertracing/jaeger/pull/6630))\n* Fix(hotrod): include ca certificates for hotrod dockerfile ([@prashant-shahi](https://github.com/prashant-shahi) in [#6627](https://github.com/jaegertracing/jaeger/pull/6627))\n* Replace all imports of jaeger/thrift-gen/* with jaeger-idl/thrift-gen/* ([@danish9039](https://github.com/danish9039) in [#6621](https://github.com/jaegertracing/jaeger/pull/6621))\n* Redefine thrift-gen types as aliases to jaeger-idl ([@danish9039](https://github.com/danish9039) in [#6619](https://github.com/jaegertracing/jaeger/pull/6619))\n* Add 'features' command to print available feature gates ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#6542](https://github.com/jaegertracing/jaeger/pull/6542))\n* Replace jaeger_image_tag with jaeger_version ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#6614](https://github.com/jaegertracing/jaeger/pull/6614))\n* Use jeager-idl/proto-gen/api_v2 ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6609](https://github.com/jaegertracing/jaeger/pull/6609))\n* Additional model/ cleanup ([@yurishkuro](https://github.com/yurishkuro) in [#6610](https://github.com/jaegertracing/jaeger/pull/6610))\n* Return 400 instead of 500 on incorrect otlp payload ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#6599](https://github.com/jaegertracing/jaeger/pull/6599))\n* Replace model imports with jaeger-idl ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6595](https://github.com/jaegertracing/jaeger/pull/6595))\n* Redefine model/ and api_v2/ types as aliases to jaeger-idl/ types ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6602](https://github.com/jaegertracing/jaeger/pull/6602))\n* Add example of es/os server_urls to configs ([@yurishkuro](https://github.com/yurishkuro) in [#6601](https://github.com/jaegertracing/jaeger/pull/6601))\n* Sanitize cassandra version before use it ([@rubenvp8510](https://github.com/rubenvp8510) in [#6596](https://github.com/jaegertracing/jaeger/pull/6596))\n* Feat: add esmapping-generator into jaeger binary ([@Rohanraj123](https://github.com/Rohanraj123) in [#6327](https://github.com/jaegertracing/jaeger/pull/6327))\n* Add replication parameter to cassandra schema script ([@asimchoudhary](https://github.com/asimchoudhary) in [#6582](https://github.com/jaegertracing/jaeger/pull/6582))\n* Exclude idl/ as a source of go code ([@yurishkuro](https://github.com/yurishkuro) in [#6591](https://github.com/jaegertracing/jaeger/pull/6591))\n* Change model.tootelxxxid() to accept id argument ([@yurishkuro](https://github.com/yurishkuro) in [#6589](https://github.com/jaegertracing/jaeger/pull/6589))\n* [refactor][storage][badger]refactored the prefilling of cache to reader ([@Manik2708](https://github.com/Manik2708) in [#6575](https://github.com/jaegertracing/jaeger/pull/6575))\n* Move span.getsamplerparams out of model/ into sampling/aggregator ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6583](https://github.com/jaegertracing/jaeger/pull/6583))\n* Remove logger parameter in adaptive/aggregator.go ([@Nabil-Salah](https://github.com/Nabil-Salah) in [#6586](https://github.com/jaegertracing/jaeger/pull/6586))\n* Separate model parts into more independent pieces ([@yurishkuro](https://github.com/yurishkuro) in [#6581](https://github.com/jaegertracing/jaeger/pull/6581))\n* [storage]generate mocks for dependency writer of v2 ([@Manik2708](https://github.com/Manik2708) in [#6576](https://github.com/jaegertracing/jaeger/pull/6576))\n* [chore] remove unused method from grpc handler ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6580](https://github.com/jaegertracing/jaeger/pull/6580))\n* Document usage of feature gates for breaking changes ([@yurishkuro](https://github.com/yurishkuro) in [#6568](https://github.com/jaegertracing/jaeger/pull/6568))\n* [refactor] move sampling strategy providers to internal/sampling/samplingstrategy ([@ary82](https://github.com/ary82) in [#6561](https://github.com/jaegertracing/jaeger/pull/6561))\n* [v2][storage] implement reverse adapter to translate v2 writer to v1 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6555](https://github.com/jaegertracing/jaeger/pull/6555))\n* [refactor] move sampling strategy interfaces to internal/sampling/strategy ([@ary82](https://github.com/ary82) in [#6547](https://github.com/jaegertracing/jaeger/pull/6547))\n* Switch v1 receivers to use v2 write path ([@yurishkuro](https://github.com/yurishkuro) in [#6532](https://github.com/jaegertracing/jaeger/pull/6532))\n* [refactor] move plugin/sampling/leaderelection to internal/leaderelection ([@ary82](https://github.com/ary82) in [#6546](https://github.com/jaegertracing/jaeger/pull/6546))\n* [refactor] move sampling http handler to internal/sampling/http ([@ary82](https://github.com/ary82) in [#6545](https://github.com/jaegertracing/jaeger/pull/6545))\n* [storage] remove dependency on archive flag in es reader ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6490](https://github.com/jaegertracing/jaeger/pull/6490))\n* [refactor] move sampling grpc handler to internal/sampling/grpc ([@ary82](https://github.com/ary82) in [#6540](https://github.com/jaegertracing/jaeger/pull/6540))\n* Correct references in cmd readme.md ([@jyoungs](https://github.com/jyoungs) in [#6539](https://github.com/jaegertracing/jaeger/pull/6539))\n* Use jaeger-v2 by default in hotrod and monitor examples ([@zzzk1](https://github.com/zzzk1) in [#6523](https://github.com/jaegertracing/jaeger/pull/6523))\n* Pass context through span processors ([@yurishkuro](https://github.com/yurishkuro) in [#6534](https://github.com/jaegertracing/jaeger/pull/6534))\n\n#### 👷 CI Improvements\n\n* Upgrade storage integration test: use v2 archive readerwriter ([@ekefan](https://github.com/ekefan) in [#6489](https://github.com/jaegertracing/jaeger/pull/6489))\n* [chore][tests] clean up integration tests to remove archive reader / writer ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6625](https://github.com/jaegertracing/jaeger/pull/6625))\n* Bump jaeger-idl ([@yurishkuro](https://github.com/yurishkuro) in [#6569](https://github.com/jaegertracing/jaeger/pull/6569))\n* [storage]upgraded integration tests to use dependency writer of storage_v2 ([@Manik2708](https://github.com/Manik2708) in [#6559](https://github.com/jaegertracing/jaeger/pull/6559))\n* [ci] fix binary-size-check workflow ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6552](https://github.com/jaegertracing/jaeger/pull/6552))\n* [ci] scrape and verify metrics at the end of e2e tests ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6330](https://github.com/jaegertracing/jaeger/pull/6330))\n* [ci] add workflow to guard against increases in the binary size ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6529](https://github.com/jaegertracing/jaeger/pull/6529))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Remove defaultprops from minimap.tsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2615](https://github.com/jaegertracing/jaeger-ui/pull/2615))\n* Remove defaultprops from scatterplot.jsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2618](https://github.com/jaegertracing/jaeger-ui/pull/2618))\n* Migrate empasizednode from class based to function based component ([@AdiIsHappy](https://github.com/AdiIsHappy) in [#2638](https://github.com/jaegertracing/jaeger-ui/pull/2638))\n* Remove defaultprops from accordiantext.tsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2612](https://github.com/jaegertracing/jaeger-ui/pull/2612))\n* Remove defaultprops from ticks.tsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2617](https://github.com/jaegertracing/jaeger-ui/pull/2617))\n* Remove defaultprops from timelinerow.tsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2616](https://github.com/jaegertracing/jaeger-ui/pull/2616))\n* Remove defaultprops from traceheader.jsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2620](https://github.com/jaegertracing/jaeger-ui/pull/2620))\n* Remove defaultprops from accordianlogs.tsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2613](https://github.com/jaegertracing/jaeger-ui/pull/2613))\n* Remove defaultprops fromt breakabletext.tsx ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2611](https://github.com/jaegertracing/jaeger-ui/pull/2611))\n* Remove defaultprops from errormessage & newwindowicon ([@ADI-ROXX](https://github.com/ADI-ROXX) in [#2609](https://github.com/jaegertracing/jaeger-ui/pull/2609))\n* [loadingindicator]: replace defaultprops with destructuring ([@its-me-abhishek](https://github.com/its-me-abhishek) in [#2601](https://github.com/jaegertracing/jaeger-ui/pull/2601))\n* [fix]: disable submit button on invalid minduration or maxduration ([@hari45678](https://github.com/hari45678) in [#2600](https://github.com/jaegertracing/jaeger-ui/pull/2600))\n* [deps]: remove dependency on redux-form ([@hari45678](https://github.com/hari45678) in [#2593](https://github.com/jaegertracing/jaeger-ui/pull/2593))\n* [fix]: remove redux-form dependency from sort selector ([@hari45678](https://github.com/hari45678) in [#2569](https://github.com/jaegertracing/jaeger-ui/pull/2569))\n* [revert]: revert redux and react-redux dependency upgrades ([@yurishkuro](https://github.com/yurishkuro) in [#2577](https://github.com/jaegertracing/jaeger-ui/pull/2577))\n* Fix: deep clone trace data for consistency ([@Zen-cronic](https://github.com/Zen-cronic) in [#2571](https://github.com/jaegertracing/jaeger-ui/pull/2571))\n* [fix]: remove redux-form dependency from monitor page ([@hari45678](https://github.com/hari45678) in [#2562](https://github.com/jaegertracing/jaeger-ui/pull/2562))\n* Fix tracediff graph pan and zoom issue ([@anshgoyalevil](https://github.com/anshgoyalevil) in [#2566](https://github.com/jaegertracing/jaeger-ui/pull/2566))\n\n#### 👷 CI Improvements\n\n* Remove unused matrix from codeql workflow ([@yurishkuro](https://github.com/yurishkuro) in [#2635](https://github.com/jaegertracing/jaeger-ui/pull/2635))\n* Rename dco->dco check ([@yurishkuro](https://github.com/yurishkuro) in [#2633](https://github.com/jaegertracing/jaeger-ui/pull/2633))\n* Add fake dco check for merge queue events ([@yurishkuro](https://github.com/yurishkuro) in [#2632](https://github.com/jaegertracing/jaeger-ui/pull/2632))\n* Don't run label check in merge queue ([@yurishkuro](https://github.com/yurishkuro) in [#2631](https://github.com/jaegertracing/jaeger-ui/pull/2631))\n* Don't run codeql from merge queue ([@yurishkuro](https://github.com/yurishkuro) in [#2630](https://github.com/jaegertracing/jaeger-ui/pull/2630))\n* Enable workflows to run in merge queue ([@yurishkuro](https://github.com/yurishkuro) in [#2629](https://github.com/jaegertracing/jaeger-ui/pull/2629))\n* [ci] fix cache resolution and syntax in check binary size workflow ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#2591](https://github.com/jaegertracing/jaeger-ui/pull/2591))\n* [ci]: add workflow to guard against growing bundle size ([@hari45678](https://github.com/hari45678) in [#2586](https://github.com/jaegertracing/jaeger-ui/pull/2586))\n\nv1.65.0 / v2.2.0 (2025-01-08)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [sampling] inherit default per-operation strategies ([@yurishkuro](https://github.com/yurishkuro) in [#6441](https://github.com/jaegertracing/jaeger/pull/6441))\n* [query] enable trace adjusters in api_v2 and api_v3 handlers ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6423](https://github.com/jaegertracing/jaeger/pull/6423))\n\n#### ✨ New Features\n\n* [feat] add time window for gettrace in span store interface ([@rim99](https://github.com/rim99) in [#6242](https://github.com/jaegertracing/jaeger/pull/6242))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Return errors from span processor creation ([@yurishkuro](https://github.com/yurishkuro) in [#6488](https://github.com/jaegertracing/jaeger/pull/6488))\n* Change collector's queue to use generics ([@yurishkuro](https://github.com/yurishkuro) in [#6486](https://github.com/jaegertracing/jaeger/pull/6486))\n* Refactor collector pipeline to allow v1/v2 data model ([@yurishkuro](https://github.com/yurishkuro) in [#6484](https://github.com/jaegertracing/jaeger/pull/6484))\n* [v2][storage] implement reverse adapter to translate v2 storage api to v1 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6485](https://github.com/jaegertracing/jaeger/pull/6485))\n* [refractor] remove dependency on tlscfg.options ([@Saumya40-codes](https://github.com/Saumya40-codes) in [#6478](https://github.com/jaegertracing/jaeger/pull/6478))\n* [query] update v1 query service to check for adapter at construction ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6482](https://github.com/jaegertracing/jaeger/pull/6482))\n* [api_v3][query] change api_v3 http handler to use v2 query service ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6459](https://github.com/jaegertracing/jaeger/pull/6459))\n* [api_v3][query] change api_v3 grpc handler to use query service v2 ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6452](https://github.com/jaegertracing/jaeger/pull/6452))\n* [v2][storage] create v2 query service to operate on otlp data model ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6343](https://github.com/jaegertracing/jaeger/pull/6343))\n* Support sampling file reload interval ([@yurishkuro](https://github.com/yurishkuro) in [#6440](https://github.com/jaegertracing/jaeger/pull/6440))\n* [jptrace] add spaniter helper function ([@yurishkuro](https://github.com/yurishkuro) in [#6407](https://github.com/jaegertracing/jaeger/pull/6407))\n* [refactor][query] propagate `rawtraces` flag to  query service ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6438](https://github.com/jaegertracing/jaeger/pull/6438))\n* [v1][adjuster] change v1 adjuster interface to not return error and modify trace in place ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6426](https://github.com/jaegertracing/jaeger/pull/6426))\n* [chore] move es/spanstore/dbmodel to internal directory ([@zzzk1](https://github.com/zzzk1) in [#6428](https://github.com/jaegertracing/jaeger/pull/6428))\n* [refactor] move model<->otlp translation from `jptrace` to `v1adapter` ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6414](https://github.com/jaegertracing/jaeger/pull/6414))\n* Enable udp ports in all-in-one ([@yurishkuro](https://github.com/yurishkuro) in [#6413](https://github.com/jaegertracing/jaeger/pull/6413))\n* [refactor] introduce helper for creating map of spans ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6406](https://github.com/jaegertracing/jaeger/pull/6406))\n* [fix] fix incorrect usage of iter package in aggregator ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6403](https://github.com/jaegertracing/jaeger/pull/6403))\n* [v2][query] implement helper to buffer sequence of traces ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6401](https://github.com/jaegertracing/jaeger/pull/6401))\n* [v2][adjuster] implement model to otlp translator with post processing ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6397](https://github.com/jaegertracing/jaeger/pull/6397))\n* [v2][adjuster] implement function to get standard adjusters to operate on otlp format ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6396](https://github.com/jaegertracing/jaeger/pull/6396))\n* [v2][adjuster] implement otlp to model translator with post processing ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6394](https://github.com/jaegertracing/jaeger/pull/6394))\n* [v2][adjuster] implement adjuster for correct timestamps for clockskew ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6392](https://github.com/jaegertracing/jaeger/pull/6392))\n* [v2][adjuster] implement adjuster for deduplicating spans ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6391](https://github.com/jaegertracing/jaeger/pull/6391))\n* Add optional time window for gettrace & searchtrace of http_handler ([@rim99](https://github.com/rim99) in [#6159](https://github.com/jaegertracing/jaeger/pull/6159))\n* [v2][adjuster] implement adjuster for sorting attributes and events ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6389](https://github.com/jaegertracing/jaeger/pull/6389))\n* Support extra custom query parameters in requests to prometheus backend ([@akstron](https://github.com/akstron) in [#6360](https://github.com/jaegertracing/jaeger/pull/6360))\n* [v2][adjuster] remove error return from adjuster interface ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6383](https://github.com/jaegertracing/jaeger/pull/6383))\n* [fix][query] filter out tracing for access to static ui assets ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6374](https://github.com/jaegertracing/jaeger/pull/6374))\n* [v2][adjuster] implement span id uniquifier adjuster to operate on otlp data model ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6367](https://github.com/jaegertracing/jaeger/pull/6367))\n* [api_v3] add time window for gettrace http_gateway ([@rim99](https://github.com/rim99) in [#6372](https://github.com/jaegertracing/jaeger/pull/6372))\n* [v2][adjuster] add warning to span links adjuster ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6381](https://github.com/jaegertracing/jaeger/pull/6381))\n* Feat: add time window for gettrace of anonymizer ([@rim99](https://github.com/rim99) in [#6368](https://github.com/jaegertracing/jaeger/pull/6368))\n* [v2][adjuster] rework adjuster interface and refactor adjusters to return implemented struct ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6362](https://github.com/jaegertracing/jaeger/pull/6362))\n* [v2][adjuster] implement otel attribute adjuster to operate on otlp data model ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6358](https://github.com/jaegertracing/jaeger/pull/6358))\n* Respond correctly to stream send error ([@yurishkuro](https://github.com/yurishkuro) in [#6357](https://github.com/jaegertracing/jaeger/pull/6357))\n* [v2][adjuster] implement ip attribute adjuster to operate on otlp data model ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6355](https://github.com/jaegertracing/jaeger/pull/6355))\n* Remove tls loading and replace with otel configtls ([@yurishkuro](https://github.com/yurishkuro) in [#6345](https://github.com/jaegertracing/jaeger/pull/6345))\n* [jaeger][v2] implement span links adjuster to operate on otlp data model ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6354](https://github.com/jaegertracing/jaeger/pull/6354))\n* [remote-storage] use otel helper instead of tlscfg ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6351](https://github.com/jaegertracing/jaeger/pull/6351))\n* Add go leak check for badgerstore, grpc and memstore e2e tests ([@Manik2708](https://github.com/Manik2708) in [#6347](https://github.com/jaegertracing/jaeger/pull/6347))\n* [v2][query] add interface for adjuster to operate on otlp data format ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6346](https://github.com/jaegertracing/jaeger/pull/6346))\n\n#### 🚧 Experimental Features\n\n* Change storage_v2 gettrace to gettraces plural ([@yurishkuro](https://github.com/yurishkuro) in [#6361](https://github.com/jaegertracing/jaeger/pull/6361))\n* Change storage v2 api to use streaming ([@yurishkuro](https://github.com/yurishkuro) in [#6359](https://github.com/jaegertracing/jaeger/pull/6359))\n\n#### 👷 CI Improvements\n\n* Upgrade storage integration tests: `dependencyreader` to v2 ([@zzzk1](https://github.com/zzzk1) in [#6477](https://github.com/jaegertracing/jaeger/pull/6477))\n* Move remaining util scripts ([@danish9039](https://github.com/danish9039) in [#6472](https://github.com/jaegertracing/jaeger/pull/6472))\n* Move lint scripts to scripts/lint ([@danish9039](https://github.com/danish9039) in [#6449](https://github.com/jaegertracing/jaeger/pull/6449))\n* Move util scripts to scripts/util ([@danish9039](https://github.com/danish9039) in [#6463](https://github.com/jaegertracing/jaeger/pull/6463))\n* Upgrade storage integration test: use `tracewriter` ([@ekefan](https://github.com/ekefan) in [#6437](https://github.com/jaegertracing/jaeger/pull/6437))\n* Move e2e scripts to scripts/e2e ([@danish9039](https://github.com/danish9039) in [#6448](https://github.com/jaegertracing/jaeger/pull/6448))\n* Move build scripts under scripts/build/ ([@danish9039](https://github.com/danish9039) in [#6446](https://github.com/jaegertracing/jaeger/pull/6446))\n* Replace apiv2 with apiv3 client in e2e tests ([@yurishkuro](https://github.com/yurishkuro) in [#6424](https://github.com/jaegertracing/jaeger/pull/6424))\n* Do not test with kafka 2.x ([@yurishkuro](https://github.com/yurishkuro) in [#6427](https://github.com/jaegertracing/jaeger/pull/6427))\n* Upgrade storage integration test to v2 trace reader ([@ekefan](https://github.com/ekefan) in [#6388](https://github.com/jaegertracing/jaeger/pull/6388))\n* Enhance kafka integration tests to support multiple kafka versions ([@zzzk1](https://github.com/zzzk1) in [#6400](https://github.com/jaegertracing/jaeger/pull/6400))\n* [fix] fix test expectations for translator to avoid depending on order ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6404](https://github.com/jaegertracing/jaeger/pull/6404))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [clean-up]: remove deprecated plexus/directedgraph ([@hari45678](https://github.com/hari45678) in [#2548](https://github.com/jaegertracing/jaeger-ui/pull/2548))\n* [fix]: make plexus demo work again ([@hari45678](https://github.com/hari45678) in [#2538](https://github.com/jaegertracing/jaeger-ui/pull/2538))\n* Upgrade from raven-js to sentry/browser ([@avinpy-255](https://github.com/avinpy-255) in [#2509](https://github.com/jaegertracing/jaeger-ui/pull/2509))\n\nv1.64.0 / v2.1.0 (2024-12-06)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [metrics][storage] move metrics reader decorator to metrics storage factory ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6287](https://github.com/jaegertracing/jaeger/pull/6287))\n* [v2][storage] move span reader decorator to storage factories ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6280](https://github.com/jaegertracing/jaeger/pull/6280))\n\n#### ✨ New Features\n\n* [v2][storage] implement read path for v2 storage interface ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6170](https://github.com/jaegertracing/jaeger/pull/6170))\n* Create cassandra db schema on session initialization ([@akstron](https://github.com/akstron) in [#5922](https://github.com/jaegertracing/jaeger/pull/5922))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix password in integration test ([@akstron](https://github.com/akstron) in [#6284](https://github.com/jaegertracing/jaeger/pull/6284))\n* [cassandra] change compaction window default to 2hrs ([@yurishkuro](https://github.com/yurishkuro) in [#6282](https://github.com/jaegertracing/jaeger/pull/6282))\n* Improve telemetry.settings ([@yurishkuro](https://github.com/yurishkuro) in [#6275](https://github.com/jaegertracing/jaeger/pull/6275))\n* [kafka] otel helper instead of tlscfg package ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6270](https://github.com/jaegertracing/jaeger/pull/6270))\n* [refactor] fix package misspelling: telemetery->telemetry ([@yurishkuro](https://github.com/yurishkuro) in [#6269](https://github.com/jaegertracing/jaeger/pull/6269))\n* [prometheus] use otel helper instead of tlscfg package ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6266](https://github.com/jaegertracing/jaeger/pull/6266))\n* [fix] use metrics decorator around metricstorage ([@yurishkuro](https://github.com/yurishkuro) in [#6262](https://github.com/jaegertracing/jaeger/pull/6262))\n* Use real metrics factory instead of nullfactory ([@yurishkuro](https://github.com/yurishkuro) in [#6261](https://github.com/jaegertracing/jaeger/pull/6261))\n* [v2] use only version number for buildinfo ([@yurishkuro](https://github.com/yurishkuro) in [#6260](https://github.com/jaegertracing/jaeger/pull/6260))\n* [refactor] move spm v2 config to cmd/jaeger/ with all other configs ([@yurishkuro](https://github.com/yurishkuro) in [#6256](https://github.com/jaegertracing/jaeger/pull/6256))\n* [es-index-cleaner] use otel helper instead of tlscfg ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6259](https://github.com/jaegertracing/jaeger/pull/6259))\n* [api_v2] change time fields in archivetracerequest to non-nullable ([@rim99](https://github.com/rim99) in [#6251](https://github.com/jaegertracing/jaeger/pull/6251))\n* [es-rollover] use otel helpers for tls config instead of tlscfg ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6238](https://github.com/jaegertracing/jaeger/pull/6238))\n* Enable usestdlibvars linter ([@mmorel-35](https://github.com/mmorel-35) in [#6249](https://github.com/jaegertracing/jaeger/pull/6249))\n* [storage_v1] add time window to gettracerequest ([@rim99](https://github.com/rim99) in [#6244](https://github.com/jaegertracing/jaeger/pull/6244))\n* [fix][query] fix misconfiguration in tls settings from using otel http helper ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6239](https://github.com/jaegertracing/jaeger/pull/6239))\n* Auto-generate gogo annotations for api_v3 ([@yurishkuro](https://github.com/yurishkuro) in [#6233](https://github.com/jaegertracing/jaeger/pull/6233))\n* Use confighttp in expvar extension ([@yurishkuro](https://github.com/yurishkuro) in [#6227](https://github.com/jaegertracing/jaeger/pull/6227))\n* Parameterize listen host and override when in container ([@yurishkuro](https://github.com/yurishkuro) in [#6231](https://github.com/jaegertracing/jaeger/pull/6231))\n* Remove 0.0.0.0 overrides in hotrod ci ([@yurishkuro](https://github.com/yurishkuro) in [#6226](https://github.com/jaegertracing/jaeger/pull/6226))\n* [storage][v2] add reader adapter that just exposes the underlying v1 reader ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6221](https://github.com/jaegertracing/jaeger/pull/6221))\n* Change start/end time in gettrace request to not be pointers ([@yurishkuro](https://github.com/yurishkuro) in [#6218](https://github.com/jaegertracing/jaeger/pull/6218))\n* Pass real meterprovider to components ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6173](https://github.com/jaegertracing/jaeger/pull/6173))\n* [v2] update versions in readme ([@yurishkuro](https://github.com/yurishkuro) in [#6206](https://github.com/jaegertracing/jaeger/pull/6206))\n* Fix: testcreatecollectorproxy unit test failing on go-tip ([@Saumya40-codes](https://github.com/Saumya40-codes) in [#6204](https://github.com/jaegertracing/jaeger/pull/6204))\n* Respect environment variables when creating internal tracer ([@akstron](https://github.com/akstron) in [#6179](https://github.com/jaegertracing/jaeger/pull/6179))\n\n#### 🚧 Experimental Features\n\n* [v2]add script for metrics markdown table ([@vvs-personalstash](https://github.com/vvs-personalstash) in [#5941](https://github.com/jaegertracing/jaeger/pull/5941))\n\n#### 👷 CI Improvements\n\n* Allow using different container runtime ([@rim99](https://github.com/rim99) in [#6247](https://github.com/jaegertracing/jaeger/pull/6247))\n* K8s integration test for hotrod ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6155](https://github.com/jaegertracing/jaeger/pull/6155))\n* Pass username/password to cassandra docker-compose health check ([@akstron](https://github.com/akstron) in [#6214](https://github.com/jaegertracing/jaeger/pull/6214))\n* [fix][ci] change the prometheus healthcheck endpoint ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6217](https://github.com/jaegertracing/jaeger/pull/6217))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Add new formatting function \"add\" ([@drewcorlin1](https://github.com/drewcorlin1) in [#2507](https://github.com/jaegertracing/jaeger-ui/pull/2507))\n* Add pad_start link formatting function #2505 ([@drewcorlin1](https://github.com/drewcorlin1) in [#2504](https://github.com/jaegertracing/jaeger-ui/pull/2504))\n* Allow formatting link parameter values as iso date #2487 ([@drewcorlin1](https://github.com/drewcorlin1) in [#2501](https://github.com/jaegertracing/jaeger-ui/pull/2501))\n\n\nv1.63.0 / v2.0.0 (2024-11-10)\n-------------------------------\n\nJaeger v2 is here! 🎉 🎉 🎉\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove jaeger-agent from distributions ([@yurishkuro](https://github.com/yurishkuro) in [#6081](https://github.com/jaegertracing/jaeger/pull/6081))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix possible null pointer deference ([@vaidikcode](https://github.com/vaidikcode) in [#6184](https://github.com/jaegertracing/jaeger/pull/6184))\n* Chore: enable all rules of perfsprint linter ([@mmorel-35](https://github.com/mmorel-35) in [#6164](https://github.com/jaegertracing/jaeger/pull/6164))\n* Chore: enable err-error and errorf rules from perfsprint linter ([@mmorel-35](https://github.com/mmorel-35) in [#6160](https://github.com/jaegertracing/jaeger/pull/6160))\n* [query] move trace handler to server level ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6147](https://github.com/jaegertracing/jaeger/pull/6147))\n* [fix][query] remove bifurcation for grpc query server ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6145](https://github.com/jaegertracing/jaeger/pull/6145))\n* [jaeger-v2] add hotrod integration test for jaeger-v2 ([@Saumya40-codes](https://github.com/Saumya40-codes) in [#6138](https://github.com/jaegertracing/jaeger/pull/6138))\n* [query] use otel's helpers for http server ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6121](https://github.com/jaegertracing/jaeger/pull/6121))\n* Use grpc interceptors instead of explicit context wrappers ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6133](https://github.com/jaegertracing/jaeger/pull/6133))\n* Fix command in v2 example ([@haoqixu](https://github.com/haoqixu) in [#6134](https://github.com/jaegertracing/jaeger/pull/6134))\n* Fix span deduplication via correct ordering of adjusters ([@cdanis](https://github.com/cdanis) in [#6116](https://github.com/jaegertracing/jaeger/pull/6116))\n* Move all query service http handlers into one function ([@yurishkuro](https://github.com/yurishkuro) in [#6128](https://github.com/jaegertracing/jaeger/pull/6128))\n* [fix][grpc] disable tracing in grpc storage writer clients ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6125](https://github.com/jaegertracing/jaeger/pull/6125))\n* Feat: automatically publish readme to docker hub ([@inosmeet](https://github.com/inosmeet) in [#6118](https://github.com/jaegertracing/jaeger/pull/6118))\n* Use grpc interceptors for bearer token ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6063](https://github.com/jaegertracing/jaeger/pull/6063))\n* [fix][query] correct query server legacy condition ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6120](https://github.com/jaegertracing/jaeger/pull/6120))\n* [query] use otel's helpers for grpc server ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6055](https://github.com/jaegertracing/jaeger/pull/6055))\n* Enable lint rule: import-shadowing ([@inosmeet](https://github.com/inosmeet) in [#6102](https://github.com/jaegertracing/jaeger/pull/6102))\n* [refractor] switch to enums for es mappings ([@Saumya40-codes](https://github.com/Saumya40-codes) in [#6091](https://github.com/jaegertracing/jaeger/pull/6091))\n* Fix rebuild-ui.sh script ([@andreasgerstmayr](https://github.com/andreasgerstmayr) in [#6098](https://github.com/jaegertracing/jaeger/pull/6098))\n* Use otel component host instead of no op host for prod code ([@chahatsagarmain](https://github.com/chahatsagarmain) in [#6085](https://github.com/jaegertracing/jaeger/pull/6085))\n* [cassandra] prevent fallback to old schema for operation names table in case of db issues ([@arunvelsriram](https://github.com/arunvelsriram) in [#6061](https://github.com/jaegertracing/jaeger/pull/6061))\n\n#### 🚧 Experimental Features\n\n* Add otlp json support for kafka e2e integration tests ([@joeyyy09](https://github.com/joeyyy09) in [#5935](https://github.com/jaegertracing/jaeger/pull/5935))\n* [v2] add es config comments ([@yurishkuro](https://github.com/yurishkuro) in [#6110](https://github.com/jaegertracing/jaeger/pull/6110))\n* [chore][docs] add documentation to elasticsearch configuration ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6103](https://github.com/jaegertracing/jaeger/pull/6103))\n* [jaeger-v2] refactor elasticsearch/opensearch configurations to have more logical groupings ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6090](https://github.com/jaegertracing/jaeger/pull/6090))\n* [jaeger-v2] implement utf-8 sanitizer for otlp ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6078](https://github.com/jaegertracing/jaeger/pull/6078))\n* [jaeger-v2] migrate elasticsearch/opensearch to use otel's tls configuration ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6079](https://github.com/jaegertracing/jaeger/pull/6079))\n* [jaeger-v2] enable queueing configuration in storage exporter ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6080](https://github.com/jaegertracing/jaeger/pull/6080))\n* [jaeger-v2] implement empty service name sanitizer for otlp ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6077](https://github.com/jaegertracing/jaeger/pull/6077))\n* [jaeger-v2] refactor elasticsearch/opensearch storage configurations ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6060](https://github.com/jaegertracing/jaeger/pull/6060))\n\n#### 👷 CI Improvements\n\n* [v2] use health check in grpc e2e test ([@yurishkuro](https://github.com/yurishkuro) in [#6113](https://github.com/jaegertracing/jaeger/pull/6113))\n* Update node.js github action to use npm lockfile, switch to latest jaeger ui ([@andreasgerstmayr](https://github.com/andreasgerstmayr) in [#6074](https://github.com/jaegertracing/jaeger/pull/6074))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Migrate from yarn v1 to npm ([@andreasgerstmayr](https://github.com/andreasgerstmayr) in [#2462](https://github.com/jaegertracing/jaeger-ui/pull/2462))\n\n#### 👷 CI Improvements\n\n* Run s390x build on push to main only ([@andreasgerstmayr](https://github.com/andreasgerstmayr) in [#2481](https://github.com/jaegertracing/jaeger-ui/pull/2481))\n\n1.62.0 / 2.0.0-rc2 (2024-10-06)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [query] change http and tls server configurations to use otel configurations ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6023](https://github.com/jaegertracing/jaeger/pull/6023))\n* [fix][spm]: change default metrics namespace to match new default in spanmetricsconnector ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6007](https://github.com/jaegertracing/jaeger/pull/6007))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [grpc storage]: propagate tenant to grpc backend ([@frzifus](https://github.com/frzifus) in [#6030](https://github.com/jaegertracing/jaeger/pull/6030))\n* [feat] deduplicate spans based on their hashcode ([@cdanis](https://github.com/cdanis) in [#6009](https://github.com/jaegertracing/jaeger/pull/6009))\n\n#### 🚧 Experimental Features\n\n* [jaeger-v2] consolidate v1 and v2 configurations for grpc storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6042](https://github.com/jaegertracing/jaeger/pull/6042))\n* [jaeger-v2] use environment variables in kafka config ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6028](https://github.com/jaegertracing/jaeger/pull/6028))\n* [jaeger-v2] align cassandra storage config with otel ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5949](https://github.com/jaegertracing/jaeger/pull/5949))\n* [jaeger-v2] refactor configuration for query service ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5998](https://github.com/jaegertracing/jaeger/pull/5998))\n* [v2] add temporary expvar extension ([@yurishkuro](https://github.com/yurishkuro) in [#5986](https://github.com/jaegertracing/jaeger/pull/5986))\n\n#### 👷 CI Improvements\n\n* [ci] disable fail fast behaviour for ci workflows ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#6052](https://github.com/jaegertracing/jaeger/pull/6052))\n* Testifylint: enable go-require ([@mmorel-35](https://github.com/mmorel-35) in [#5983](https://github.com/jaegertracing/jaeger/pull/5983))\n* Fix regex for publishing v2 image ([@yurishkuro](https://github.com/yurishkuro) in [#5988](https://github.com/jaegertracing/jaeger/pull/5988))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Support uploads of .jsonl files ([@Saumya40-codes](https://github.com/Saumya40-codes) in [#2461](https://github.com/jaegertracing/jaeger-ui/pull/2461))\n\n\n1.61.0 / 2.0.0-rc1 (2024-09-14)\n-------------------------------\n\n### Backend Changes\n\nThis release contains an official pre-release candidate of Jaeger v2, as binary and Docker image `jaeger`.\n\n#### ⛔ Breaking Changes\n\n* Remove support for cassandra 3.x and add cassandra 5.x ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5962](https://github.com/jaegertracing/jaeger/pull/5962))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Fix: the 'tagtype' in es jaeger-span mapping tags.properties should be 'type' ([@chinaran](https://github.com/chinaran) in [#5980](https://github.com/jaegertracing/jaeger/pull/5980))\n* Add readme for adaptive sampling ([@yurishkuro](https://github.com/yurishkuro) in [#5955](https://github.com/jaegertracing/jaeger/pull/5955))\n* [adaptive sampling] clean-up after previous refactoring ([@yurishkuro](https://github.com/yurishkuro) in [#5954](https://github.com/jaegertracing/jaeger/pull/5954))\n* [adaptive processor] remove redundant function ([@yurishkuro](https://github.com/yurishkuro) in [#5953](https://github.com/jaegertracing/jaeger/pull/5953))\n* [jaeger-v2] consolidate options and namespaceconfig for badger storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5937](https://github.com/jaegertracing/jaeger/pull/5937))\n* Remove unused \"namespace\" field from badger config ([@yurishkuro](https://github.com/yurishkuro) in [#5929](https://github.com/jaegertracing/jaeger/pull/5929))\n* Simplify bundling of ui assets ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5917](https://github.com/jaegertracing/jaeger/pull/5917))\n* Clean up grpc storage config ([@yurishkuro](https://github.com/yurishkuro) in [#5877](https://github.com/jaegertracing/jaeger/pull/5877))\n* Add script to replace apache headers with spdx ([@thecaffeinedev](https://github.com/thecaffeinedev) in [#5808](https://github.com/jaegertracing/jaeger/pull/5808))\n* Add copyright/license headers to script files ([@Zen-cronic](https://github.com/Zen-cronic) in [#5829](https://github.com/jaegertracing/jaeger/pull/5829))\n* Clearer output from lint scripts ([@yurishkuro](https://github.com/yurishkuro) in [#5820](https://github.com/jaegertracing/jaeger/pull/5820))\n\n#### 🚧 Experimental Features\n\n* [jaeger-v2] add validation and comments to badger storage config ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5927](https://github.com/jaegertracing/jaeger/pull/5927))\n* [jaeger-v2] add validation and comments to memory storage config ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5925](https://github.com/jaegertracing/jaeger/pull/5925))\n* Support tail based sampling processor from otel collector extension ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5878](https://github.com/jaegertracing/jaeger/pull/5878))\n* [v2] configure health check extension for all configs ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5861](https://github.com/jaegertracing/jaeger/pull/5861))\n* [v2] add legacy formats into e2e kafka integration tests ([@joeyyy09](https://github.com/joeyyy09) in [#5824](https://github.com/jaegertracing/jaeger/pull/5824))\n* [v2] configure healthcheck extension ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5831](https://github.com/jaegertracing/jaeger/pull/5831))\n* Added _total suffix to otel counter metrics. ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5810](https://github.com/jaegertracing/jaeger/pull/5810))\n\n#### 👷 CI Improvements\n\n* Release v2 cleanup 3 ([@yurishkuro](https://github.com/yurishkuro) in [#5984](https://github.com/jaegertracing/jaeger/pull/5984))\n* Replace loopvar linter ([@anishbista60](https://github.com/anishbista60) in [#5976](https://github.com/jaegertracing/jaeger/pull/5976))\n* Stop using v1 and v1.x tags for docker images ([@yurishkuro](https://github.com/yurishkuro) in [#5956](https://github.com/jaegertracing/jaeger/pull/5956))\n* V2 repease prep ([@yurishkuro](https://github.com/yurishkuro) in [#5932](https://github.com/jaegertracing/jaeger/pull/5932))\n* Normalize build-binaries targets ([@yurishkuro](https://github.com/yurishkuro) in [#5924](https://github.com/jaegertracing/jaeger/pull/5924))\n* Fix integration test log dumping for storage backends ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#5915](https://github.com/jaegertracing/jaeger/pull/5915))\n* Add jaeger-v2 binary as new release artifact ([@renovate-bot](https://github.com/renovate-bot) in [#5893](https://github.com/jaegertracing/jaeger/pull/5893))\n* [ci] add support for v2 tags during build ([@yurishkuro](https://github.com/yurishkuro) in [#5890](https://github.com/jaegertracing/jaeger/pull/5890))\n* Add hardcoded db password and username to cassandra integration test ([@Ali-Alnosairi](https://github.com/Ali-Alnosairi) in [#5805](https://github.com/jaegertracing/jaeger/pull/5805))\n* Define contents permissions on \"dependabot validate\" workflow ([@mmorel-35](https://github.com/mmorel-35) in [#5874](https://github.com/jaegertracing/jaeger/pull/5874))\n* [fix] print kafka logs on test failure ([@joeyyy09](https://github.com/joeyyy09) in [#5873](https://github.com/jaegertracing/jaeger/pull/5873))\n* Pin github actions dependencies ([@harshitasao](https://github.com/harshitasao) in [#5860](https://github.com/jaegertracing/jaeger/pull/5860))\n* Add go.mod for docker debug image ([@hellspawn679](https://github.com/hellspawn679) in [#5852](https://github.com/jaegertracing/jaeger/pull/5852))\n* Enable lint rule: redefines-builtin-id ([@ZXYxc](https://github.com/ZXYxc) in [#5791](https://github.com/jaegertracing/jaeger/pull/5791))\n* Require manual go version updates for patch versions ([@wasup-yash](https://github.com/wasup-yash) in [#5848](https://github.com/jaegertracing/jaeger/pull/5848))\n* Clean up obselete 'version' tag from docker-compose files ([@vvs-personalstash](https://github.com/vvs-personalstash) in [#5826](https://github.com/jaegertracing/jaeger/pull/5826))\n* Update expected codecov flags count to 19 ([@yurishkuro](https://github.com/yurishkuro) in [#5811](https://github.com/jaegertracing/jaeger/pull/5811))\n\n\n### 📊 UI Changes\n\nDependencies upgrades only.\n\n\n1.60.0 / 2.0.0-rc0 (2024-08-06)\n-------------------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Completely remove \"grpc-plugin\" as storage type ([@yurishkuro](https://github.com/yurishkuro) in [#5741](https://github.com/jaegertracing/jaeger/pull/5741))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Do not use image tag without version ([@yurishkuro](https://github.com/yurishkuro) in [#5783](https://github.com/jaegertracing/jaeger/pull/5783))\n* Only attach :latest tag to versioned images from main ([@yurishkuro](https://github.com/yurishkuro) in [#5781](https://github.com/jaegertracing/jaeger/pull/5781))\n* Add references to jaeger v2 ([@yurishkuro](https://github.com/yurishkuro) in [#5779](https://github.com/jaegertracing/jaeger/pull/5779))\n* Ensure hotrod image is published at the end of e2e test ([@yurishkuro](https://github.com/yurishkuro) in [#5764](https://github.com/jaegertracing/jaeger/pull/5764))\n* [bug] [hotrod] delay env var mapping until logger is initialized ([@yurishkuro](https://github.com/yurishkuro) in [#5760](https://github.com/jaegertracing/jaeger/pull/5760))\n* Make otlp receiver listen on all ips again ([@yurishkuro](https://github.com/yurishkuro) in [#5739](https://github.com/jaegertracing/jaeger/pull/5739))\n* [hotrod] fix connectivity in docker compose ([@yurishkuro](https://github.com/yurishkuro) in [#5734](https://github.com/jaegertracing/jaeger/pull/5734))\n\n#### 🚧 Experimental Features\n\n* [v2] enable remote sampling extension and include in e2e tests ([@yurishkuro](https://github.com/yurishkuro) in [#5802](https://github.com/jaegertracing/jaeger/pull/5802))\n* Ensure similar naming for storage write metrics ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5798](https://github.com/jaegertracing/jaeger/pull/5798))\n* [v2] ensure similar naming for query service metrics ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5785](https://github.com/jaegertracing/jaeger/pull/5785))\n* Configure otel collector to observe internal telemetry ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5752](https://github.com/jaegertracing/jaeger/pull/5752))\n* Add kafka exporter and receiver configuration ([@joeyyy09](https://github.com/joeyyy09) in [#5703](https://github.com/jaegertracing/jaeger/pull/5703))\n* Enable spm in jaeger v2 ([@FlamingSaint](https://github.com/FlamingSaint) in [#5681](https://github.com/jaegertracing/jaeger/pull/5681))\n* [jaeger-v2] add `remotesampling` extension ([@Pushkarm029](https://github.com/Pushkarm029) in [#5389](https://github.com/jaegertracing/jaeger/pull/5389))\n* Created telset for remote-storage component ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5731](https://github.com/jaegertracing/jaeger/pull/5731))\n\n#### 👷 CI Improvements\n\n* Unpin codeql actions ([@yurishkuro](https://github.com/yurishkuro) in [#5787](https://github.com/jaegertracing/jaeger/pull/5787))\n* Skip building hotrod for all platforms for pull requests ([@Manoramsharma](https://github.com/Manoramsharma) in [#5765](https://github.com/jaegertracing/jaeger/pull/5765))\n* Add a threshold for expected zero values in the spm script ([@FlamingSaint](https://github.com/FlamingSaint) in [#5753](https://github.com/jaegertracing/jaeger/pull/5753))\n* [v2] add e2e test with memory store ([@yurishkuro](https://github.com/yurishkuro) in [#5751](https://github.com/jaegertracing/jaeger/pull/5751))\n* Rationalize naming of gha workflow files ([@yurishkuro](https://github.com/yurishkuro) in [#5750](https://github.com/jaegertracing/jaeger/pull/5750))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Allow uploading json-per-line otlp data ([@BenzeneAlcohol](https://github.com/BenzeneAlcohol) in [#2380](https://github.com/jaegertracing/jaeger-ui/pull/2380))\n\n\n1.59.0 (2024-07-10)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* The OTEL Collector upgrade brought in a change where OTLP receivers started listening on `localhost` instead of `0.0.0.0` as before. As a result, when running in container environment the endpoints are likely unreachable from other containers (Issue [#5737](https://github.com/jaegertracing/jaeger/issues/5737)). The fix will be available in the next release. Meanwhile, the workaround is to instruct Jaeger to listen on `0.0.0.0`, as in [this fix](https://github.com/jaegertracing/jaeger/pull/5734/files#diff-299f817cc4ab077ddb763f1e6a023d9d042d714e2fd3736cc40af3f218d44f1eR15):\n```\n      - COLLECTOR_OTLP_GRPC_HOST_PORT=0.0.0.0:4317\n      - COLLECTOR_OTLP_HTTP_HOST_PORT=0.0.0.0:4318\n```\n* Update opentelemetry-go to v1.28.0 and refactor references to semantic conventions ([@renovate-bot](https://github.com/renovate-bot) in [#5698](https://github.com/jaegertracing/jaeger/pull/5698))\n\n#### ✨ New Features\n\n* Run  jaeger-es-index-cleaner and jaeger-es-rollover locally ([@hellspawn679](https://github.com/hellspawn679) in [#5714](https://github.com/jaegertracing/jaeger/pull/5714))\n* [tracegen] allow use of adaptive sampling ([@yurishkuro](https://github.com/yurishkuro) in [#5718](https://github.com/jaegertracing/jaeger/pull/5718))\n* [v2] add v1 factory converter to v2 storage factory ([@james-ryans](https://github.com/james-ryans) in [#5497](https://github.com/jaegertracing/jaeger/pull/5497))\n* Upgrade badger  v3->badger  v4 ([@hellspawn679](https://github.com/hellspawn679) in [#5619](https://github.com/jaegertracing/jaeger/pull/5619))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Cleanup the prometheus config ([@FlamingSaint](https://github.com/FlamingSaint) in [#5720](https://github.com/jaegertracing/jaeger/pull/5720))\n* Upgrade microsim to v0.4.1 ([@FlamingSaint](https://github.com/FlamingSaint) in [#5702](https://github.com/jaegertracing/jaeger/pull/5702))\n* Add all mocks to mockery config file and regenerate ([@danish9039](https://github.com/danish9039) in [#5626](https://github.com/jaegertracing/jaeger/pull/5626))\n* Add better logging options ([@yurishkuro](https://github.com/yurishkuro) in [#5675](https://github.com/jaegertracing/jaeger/pull/5675))\n* Restore \"operation\" name in the metrics response ([@yurishkuro](https://github.com/yurishkuro) in [#5673](https://github.com/jaegertracing/jaeger/pull/5673))\n* Add flag for custom authenticators in cassandra storage ([@hellspawn679](https://github.com/hellspawn679) in [#5628](https://github.com/jaegertracing/jaeger/pull/5628))\n* Rename strategy store to sampling strategy provider ([@yurishkuro](https://github.com/yurishkuro) in [#5634](https://github.com/jaegertracing/jaeger/pull/5634))\n* [query] avoid errors when closing shared listener ([@vermaaatul07](https://github.com/vermaaatul07) in [#5559](https://github.com/jaegertracing/jaeger/pull/5559))\n* Bump github.com/golangci/golangci-lint from 1.55.2 to 1.59.1 and fix linter errors ([@FlamingSaint](https://github.com/FlamingSaint) in [#5579](https://github.com/jaegertracing/jaeger/pull/5579))\n* Fix binary path in package-deploy.sh ([@yurishkuro](https://github.com/yurishkuro) in [#5561](https://github.com/jaegertracing/jaeger/pull/5561))\n\n#### 🚧 Experimental Features\n\n* Implement telemetry struct for v1 components initialization ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5695](https://github.com/jaegertracing/jaeger/pull/5695))\n* Support default configs for storage backends ([@yurishkuro](https://github.com/yurishkuro) in [#5691](https://github.com/jaegertracing/jaeger/pull/5691))\n* Simplify configs organization ([@yurishkuro](https://github.com/yurishkuro) in [#5690](https://github.com/jaegertracing/jaeger/pull/5690))\n* Create metrics.factory adapter for otel metrics ([@Wise-Wizard](https://github.com/Wise-Wizard) in [#5661](https://github.com/jaegertracing/jaeger/pull/5661))\n\n#### 👷 CI Improvements\n\n* Apply 'latest' tag to latest published snapshot images ([@yurishkuro](https://github.com/yurishkuro) in [#5724](https://github.com/jaegertracing/jaeger/pull/5724))\n* [bug] use correct argument as jaeger-version ([@yurishkuro](https://github.com/yurishkuro) in [#5716](https://github.com/jaegertracing/jaeger/pull/5716))\n* Add spm integration tests ([@hellspawn679](https://github.com/hellspawn679) in [#5640](https://github.com/jaegertracing/jaeger/pull/5640))\n* Add spm build to ci ([@yurishkuro](https://github.com/yurishkuro) in [#5663](https://github.com/jaegertracing/jaeger/pull/5663))\n* Remove unnecessary .nocover files ([@yurishkuro](https://github.com/yurishkuro) in [#5642](https://github.com/jaegertracing/jaeger/pull/5642))\n* Add tests for anonymizer/app/query. ([@shanukun](https://github.com/shanukun) in [#5638](https://github.com/jaegertracing/jaeger/pull/5638))\n* Add alternate way to install gotip ([@EraKin575](https://github.com/EraKin575) in [#5618](https://github.com/jaegertracing/jaeger/pull/5618))\n* Add semver to dependencies ([@danish9039](https://github.com/danish9039) in [#5590](https://github.com/jaegertracing/jaeger/pull/5590))\n* Create config file for mockery instead of using explicit cli flags in the makefile ([@jesslourenco](https://github.com/jesslourenco) in [#5623](https://github.com/jaegertracing/jaeger/pull/5623))\n* Update renovate bot to not apply patches to e2e test dependencies ([@DustyMMiller](https://github.com/DustyMMiller) in [#5622](https://github.com/jaegertracing/jaeger/pull/5622))\n* Require renovate bot to run go mod tidy ([@yurishkuro](https://github.com/yurishkuro) in [#5612](https://github.com/jaegertracing/jaeger/pull/5612))\n* Fix new warnings from the linter upgrade ([@WaterLemons2k](https://github.com/WaterLemons2k) in [#5589](https://github.com/jaegertracing/jaeger/pull/5589))\n* [ci] validate that generated mocks are up to date ([@yurishkuro](https://github.com/yurishkuro) in [#5568](https://github.com/jaegertracing/jaeger/pull/5568))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Add escaped example to tag search help popup ([@yurishkuro](https://github.com/yurishkuro) in [#2354](https://github.com/jaegertracing/jaeger-ui/pull/2354))\n\n\n\n1.58.1 (2024-06-22)\n-------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* SPM: Restore \"operation\" name in the metrics response ([@yurishkuro](https://github.com/yurishkuro) in [#5673](https://github.com/jaegertracing/jaeger/pull/5673))\n\n\n1.58.0 (2024-06-11)\n-------------------\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove support for otel spanmetrics processor ([@varshith257](https://github.com/varshith257) in [#5539](https://github.com/jaegertracing/jaeger/pull/5539))\n* Remove support for expvar-backed metrics ([@joeyyy09](https://github.com/joeyyy09) in [#5437](https://github.com/jaegertracing/jaeger/pull/5437))\n* Remove support for elasticsearch v5/v6 ([@FlamingSaint](https://github.com/FlamingSaint) in [#5448](https://github.com/jaegertracing/jaeger/pull/5448))\n* Remove support for gRPC-Plugin ([@h4shk4t](https://github.com/h4shk4t) in [#5388](https://github.com/jaegertracing/jaeger/pull/5388))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Set desired providers/converters instead of relying on defaults ([@TylerHelmuth](https://github.com/TylerHelmuth) in [#5543](https://github.com/jaegertracing/jaeger/pull/5543))\n* Add elasticsearch helper binaries to release ([@FlamingSaint](https://github.com/FlamingSaint) in [#5501](https://github.com/jaegertracing/jaeger/pull/5501))\n* Replace internal metrics.factory usage with direct calls to expvar ([@prakrit55](https://github.com/prakrit55) in [#5496](https://github.com/jaegertracing/jaeger/pull/5496))\n* [refactor] move root span handler into aggregator ([@Pushkarm029](https://github.com/Pushkarm029) in [#5478](https://github.com/jaegertracing/jaeger/pull/5478))\n* Refactor adaptive sampling aggregator & strategy store ([@Pushkarm029](https://github.com/Pushkarm029) in [#5441](https://github.com/jaegertracing/jaeger/pull/5441))\n* [remote-storage] add healthcheck to grpc server ([@james-ryans](https://github.com/james-ryans) in [#5461](https://github.com/jaegertracing/jaeger/pull/5461))\n* Fix alpine image to 3.19.0 ([@prakrit55](https://github.com/prakrit55) in [#5454](https://github.com/jaegertracing/jaeger/pull/5454))\n* Replace grpc-plugin storage type name with just grpc ([@yurishkuro](https://github.com/yurishkuro) in [#5442](https://github.com/jaegertracing/jaeger/pull/5442))\n* [grpc-storage] use grpc.newclient ([@yurishkuro](https://github.com/yurishkuro) in [#5393](https://github.com/jaegertracing/jaeger/pull/5393))\n* Replace public initfromoptions with private configurefromoptions ([@yurishkuro](https://github.com/yurishkuro) in [#5417](https://github.com/jaegertracing/jaeger/pull/5417))\n* [jaeger-v2] fix e2e storage integration v0.99.0 otel col failing ([@james-ryans](https://github.com/james-ryans) in [#5419](https://github.com/jaegertracing/jaeger/pull/5419))\n* Add purge method for cassandra ([@akagami-harsh](https://github.com/akagami-harsh) in [#5414](https://github.com/jaegertracing/jaeger/pull/5414))\n* [v2] add diagrams to the docs ([@yurishkuro](https://github.com/yurishkuro) in [#5412](https://github.com/jaegertracing/jaeger/pull/5412))\n* [es] remove unused indexcache ([@yurishkuro](https://github.com/yurishkuro) in [#5408](https://github.com/jaegertracing/jaeger/pull/5408))\n\n#### 🚧 Experimental Features\n\n* Create new grpc storage configuration to align with otel ([@akagami-harsh](https://github.com/akagami-harsh) in [#5331](https://github.com/jaegertracing/jaeger/pull/5331))\n\n#### 👷 CI Improvements\n\n* Add workflow to validate dependabot config ([@yurishkuro](https://github.com/yurishkuro) in [#5556](https://github.com/jaegertracing/jaeger/pull/5556))\n* Create separate directories for docker compose files ([@FlamingSaint](https://github.com/FlamingSaint) in [#5554](https://github.com/jaegertracing/jaeger/pull/5554))\n* [ci] use m.x version names in workflows ([@hellspawn679](https://github.com/hellspawn679) in [#5546](https://github.com/jaegertracing/jaeger/pull/5546))\n* Enable lint rule: unused-parameter ([@FlamingSaint](https://github.com/FlamingSaint) in [#5534](https://github.com/jaegertracing/jaeger/pull/5534))\n* Add a new workflow for testing the release workflow ([@yurishkuro](https://github.com/yurishkuro) in [#5532](https://github.com/jaegertracing/jaeger/pull/5532))\n* Enable lint rules: struct-tag & unexported-return ([@FlamingSaint](https://github.com/FlamingSaint) in [#5533](https://github.com/jaegertracing/jaeger/pull/5533))\n* Enable lint rules: early-return & indent-error-flow ([@FlamingSaint](https://github.com/FlamingSaint) in [#5526](https://github.com/jaegertracing/jaeger/pull/5526))\n* Enable lint rule: exported ([@FlamingSaint](https://github.com/FlamingSaint) in [#5525](https://github.com/jaegertracing/jaeger/pull/5525))\n* Enable lint rules: confusing-results & receiver-naming ([@FlamingSaint](https://github.com/FlamingSaint) in [#5524](https://github.com/jaegertracing/jaeger/pull/5524))\n* Add manual dco check using python script dco-check ([@yurishkuro](https://github.com/yurishkuro) in [#5528](https://github.com/jaegertracing/jaeger/pull/5528))\n* Use docker compose for cassandra integration tests ([@hellspawn679](https://github.com/hellspawn679) in [#5520](https://github.com/jaegertracing/jaeger/pull/5520))\n* Enable lint rule: modifies-value-receiver ([@FlamingSaint](https://github.com/FlamingSaint) in [#5517](https://github.com/jaegertracing/jaeger/pull/5517))\n* Enable lint rule: unused-receiver ([@FlamingSaint](https://github.com/FlamingSaint) in [#5521](https://github.com/jaegertracing/jaeger/pull/5521))\n* Enable lint rule: dot-imports ([@FlamingSaint](https://github.com/FlamingSaint) in [#5513](https://github.com/jaegertracing/jaeger/pull/5513))\n* Enable lint rules: bare-return & empty-lines ([@FlamingSaint](https://github.com/FlamingSaint) in [#5512](https://github.com/jaegertracing/jaeger/pull/5512))\n* Manage 3rd party tools via dedicated go.mod ([@yurishkuro](https://github.com/yurishkuro) in [#5509](https://github.com/jaegertracing/jaeger/pull/5509))\n* Enable lint rule: use-any ([@FlamingSaint](https://github.com/FlamingSaint) in [#5510](https://github.com/jaegertracing/jaeger/pull/5510))\n* Enable lint rule: unexported-naming ([@FlamingSaint](https://github.com/FlamingSaint) in [#5511](https://github.com/jaegertracing/jaeger/pull/5511))\n* Add nolintlint linter ([@yurishkuro](https://github.com/yurishkuro) in [#5508](https://github.com/jaegertracing/jaeger/pull/5508))\n* Use docker compose for kafka integration tests ([@hellspawn679](https://github.com/hellspawn679) in [#5500](https://github.com/jaegertracing/jaeger/pull/5500))\n* Use docker compose for elasticsearch/opensearch integration tests ([@hellspawn679](https://github.com/hellspawn679) in [#5490](https://github.com/jaegertracing/jaeger/pull/5490))\n* Split v1 and v2 es/os integration tests ([@yurishkuro](https://github.com/yurishkuro) in [#5487](https://github.com/jaegertracing/jaeger/pull/5487))\n* Remove args and depict the image in from directive ([@prakrit55](https://github.com/prakrit55) in [#5465](https://github.com/jaegertracing/jaeger/pull/5465))\n* [v2] remove retries and increase timeout for e2e tests ([@james-ryans](https://github.com/james-ryans) in [#5460](https://github.com/jaegertracing/jaeger/pull/5460))\n* Restore code coverage threshold back to 95% ([@varshith257](https://github.com/varshith257) in [#5457](https://github.com/jaegertracing/jaeger/pull/5457))\n* [v2] add logging to read/write spans in e2e tests ([@james-ryans](https://github.com/james-ryans) in [#5456](https://github.com/jaegertracing/jaeger/pull/5456))\n* Remove elasticsearch v5/v6 from tests ([@FlamingSaint](https://github.com/FlamingSaint) in [#5451](https://github.com/jaegertracing/jaeger/pull/5451))\n* [v2] replace e2e span_reader grpc.dialcontext with newclient ([@james-ryans](https://github.com/james-ryans) in [#5443](https://github.com/jaegertracing/jaeger/pull/5443))\n* Stop running integration tests for elasticsearch v5/v6 ([@yurishkuro](https://github.com/yurishkuro) in [#5440](https://github.com/jaegertracing/jaeger/pull/5440))\n* [v2] remove temporary skipbinaryattrs flag from e2e tests ([@james-ryans](https://github.com/james-ryans) in [#5436](https://github.com/jaegertracing/jaeger/pull/5436))\n* [v2] dump storage docker logs in github ci if e2e test failed ([@james-ryans](https://github.com/james-ryans) in [#5433](https://github.com/jaegertracing/jaeger/pull/5433))\n* [v2] change e2e jaeger-v2 binary log output to temp file ([@james-ryans](https://github.com/james-ryans) in [#5431](https://github.com/jaegertracing/jaeger/pull/5431))\n* [bug] fix syntax for invoking upload-codecov action ([@yurishkuro](https://github.com/yurishkuro) in [#5416](https://github.com/jaegertracing/jaeger/pull/5416))\n* Use helper action to retry codecov uploads ([@yurishkuro](https://github.com/yurishkuro) in [#5411](https://github.com/jaegertracing/jaeger/pull/5411))\n* Only build docker images for crossdock tests for linux/amd64 ([@varshith257](https://github.com/varshith257) in [#5410](https://github.com/jaegertracing/jaeger/pull/5410))\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Document how to debug unit tests in vscode ([@RISHIKESHk07](https://github.com/RISHIKESHk07) in [#2297](https://github.com/jaegertracing/jaeger-ui/pull/2297))\n\n#### 👷 CI Improvements\n\n* Github actions added to block prs from fork/main branch ([@varshith257](https://github.com/varshith257) in [#2296](https://github.com/jaegertracing/jaeger-ui/pull/2296))\n\n1.57.0 (2024-05-01)\n-------------------\n\n### Backend Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* [jaeger-v2] define an internal interface of storage v2 spanstore ([@james-ryans](https://github.com/james-ryans) in [#5399](https://github.com/jaegertracing/jaeger/pull/5399))\n* Combine jaeger ui release notes with jaeger backend ([@albertteoh](https://github.com/albertteoh) in [#5405](https://github.com/jaegertracing/jaeger/pull/5405))\n* [agent] use grpc.newclient ([@yurishkuro](https://github.com/yurishkuro) in [#5392](https://github.com/jaegertracing/jaeger/pull/5392))\n* [sampling] fix merging of per-operation strategies into service strategies without them ([@kuujis](https://github.com/kuujis) in [#5277](https://github.com/jaegertracing/jaeger/pull/5277))\n* Create sampling templates when creating sampling store ([@JaeguKim](https://github.com/JaeguKim) in [#5349](https://github.com/jaegertracing/jaeger/pull/5349))\n* [kafka-consumer] set the rackid in consumer config ([@sappusaketh](https://github.com/sappusaketh) in [#5374](https://github.com/jaegertracing/jaeger/pull/5374))\n* Adding best practices badge to readme.md ([@jkowall](https://github.com/jkowall) in [#5369](https://github.com/jaegertracing/jaeger/pull/5369))\n\n#### 👷 CI Improvements\n\n* Moving global write permissions down into the ci jobs ([@jkowall](https://github.com/jkowall) in [#5370](https://github.com/jaegertracing/jaeger/pull/5370))\n\n\n### 📊 UI Changes\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Improve trace page title with data and unique emoji (fixes #2256) ([@nox](https://github.com/nox) in [#2275](https://github.com/jaegertracing/jaeger-ui/pull/2275))\n* Require node version 20+ ([@Baalekshan](https://github.com/Baalekshan) in [#2274](https://github.com/jaegertracing/jaeger-ui/pull/2274))\n\n\n1.56.0 (2024-04-02)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Fix hotrod instructions ([@yurishkuro](https://github.com/yurishkuro) in [#5273](https://github.com/jaegertracing/jaeger/pull/5273))\n\n#### 🐞 Bug fixes, Minor Improvements\n\n* Refactor healthcheck signalling between server and service ([@WillSewell](https://github.com/WillSewell) in [#5308](https://github.com/jaegertracing/jaeger/pull/5308))\n* Docs: badger file permission as non-root service ([@tico88612](https://github.com/tico88612) in [#5282](https://github.com/jaegertracing/jaeger/pull/5282))\n* [kafka-consumer] add support for setting fetch message max bytes ([@sappusaketh](https://github.com/sappusaketh) in [#5283](https://github.com/jaegertracing/jaeger/pull/5283))\n* [chore] remove repetitive words ([@tgolang](https://github.com/tgolang) in [#5265](https://github.com/jaegertracing/jaeger/pull/5265))\n* Fix zipkin spanformat ([@fyuan1316](https://github.com/fyuan1316) in [#5261](https://github.com/jaegertracing/jaeger/pull/5261))\n* [kafka-producer] support setting max message size ([@sappusaketh](https://github.com/sappusaketh) in [#5263](https://github.com/jaegertracing/jaeger/pull/5263))\n\n#### 🚧 Experimental Features\n\n* [jaeger-v2] add support for opensearch ([@akagami-harsh](https://github.com/akagami-harsh) in [#5257](https://github.com/jaegertracing/jaeger/pull/5257))\n* [jaeger-v2] add support for cassandra ([@Pushkarm029](https://github.com/Pushkarm029) in [#5253](https://github.com/jaegertracing/jaeger/pull/5253))\n\n#### 👷 CI Improvements\n\n* Allow go-leak linter to fail ci ([@yurishkuro](https://github.com/yurishkuro) in [#5316](https://github.com/jaegertracing/jaeger/pull/5316))\n* [jaeger-v2] add grpc storage backend integration test ([@james-ryans](https://github.com/james-ryans) in [#5259](https://github.com/jaegertracing/jaeger/pull/5259))\n* Github actions added to block prs from fork/main branch ([@varshith257](https://github.com/varshith257) in [#5272](https://github.com/jaegertracing/jaeger/pull/5272))\n\n\n### 📊 UI Changes\n\n* UI pinned to version [1.40.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1400-2024-04-02).\n\n\n1.55.0 (2024-03-04)\n-------------------\n### Backend Changes\n\n#### ✨ New Features:\n\n* Support uploading traces to UI in OpenTelemetry format (OTLP/JSON) ([@NavinShrinivas](https://github.com/NavinShrinivas) in [#5155](https://github.com/jaegertracing/jaeger/pull/5155))\n* Add Elasticsearch storage support for adaptive sampling ([@Pushkarm029](https://github.com/Pushkarm029) in [#5158](https://github.com/jaegertracing/jaeger/pull/5158))\n\n#### 🐞 Bug fixes, Minor Improvements:\n\n* Add the `print-config` subcommand ([@gmafrac](https://github.com/gmafrac) in [#5200](https://github.com/jaegertracing/jaeger/pull/5200))\n* Return more detailed errors from ES storage ([@yurishkuro](https://github.com/yurishkuro) in [#5209](https://github.com/jaegertracing/jaeger/pull/5209))\n* Bump go version ([@yurishkuro](https://github.com/yurishkuro) in [#5180](https://github.com/jaegertracing/jaeger/pull/5180))\n\n#### 🚧 Experimental Features:\n\n* [jaeger-v2] Add support for gRPC storarge ([@james-ryans](https://github.com/james-ryans) in [#5228](https://github.com/jaegertracing/jaeger/pull/5228))\n* [jaeger-v2] Add support for Elasticsearch ([@akagami-harsh](https://github.com/akagami-harsh) in [#5152](https://github.com/jaegertracing/jaeger/pull/5152))\n\n### 📊 UI Changes\n\n* UI pinned to version [1.39.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1390-2024-03-04).\n\n\n1.54.0 (2024-02-06)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes:\n\n* Remove remnants of internal otlp types ([@yurishkuro](https://github.com/yurishkuro) in [#5107](https://github.com/jaegertracing/jaeger/pull/5107))\n* Use official otlp types in api_v3 and avoid triple-marshaling ([@yurishkuro](https://github.com/yurishkuro) in [#5098](https://github.com/jaegertracing/jaeger/pull/5098))\n\n#### ✨ New Features:\n\n* [jaeger-v2] add support for badger ([@akagami-harsh](https://github.com/akagami-harsh) in [#5112](https://github.com/jaegertracing/jaeger/pull/5112))\n\n#### 🐞 Bug fixes, Minor Improvements:\n\n* [jaeger-v2] streamline storage initialization ([@yurishkuro](https://github.com/yurishkuro) in [#5171](https://github.com/jaegertracing/jaeger/pull/5171))\n* Replace security self-assesment with one from cncf/tag-security ([@jkowall](https://github.com/jkowall) in [#5142](https://github.com/jaegertracing/jaeger/pull/5142))\n* Avoid changing a correct order of span references ([@sherwoodwang](https://github.com/sherwoodwang) in [#5121](https://github.com/jaegertracing/jaeger/pull/5121))\n\n#### 👷 CI Improvements:\n\n* Remove test summary reports ([@albertteoh](https://github.com/albertteoh) in [#5126](https://github.com/jaegertracing/jaeger/pull/5126))\n\n### UI Changes\n\n* UI pinned to version [1.38.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1380-2024-02-06).\n\n1.53.0 (2024-01-08)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes:\n\n* 💤 swap zipkin server for zipkin receiver from otel collector contrib ([@yurishkuro](https://github.com/yurishkuro) in [#5045](https://github.com/jaegertracing/jaeger/pull/5045))\n* Make all-in-one metric names match metrics from standalone components ([@yurishkuro](https://github.com/yurishkuro) in [#5008](https://github.com/jaegertracing/jaeger/pull/5008))\n\n#### 🐞 Bug fixes, Minor Improvements:\n\n* Upgrade thrift compiler to v0.19 and regenerate types ([@yurishkuro](https://github.com/yurishkuro) in [#5080](https://github.com/jaegertracing/jaeger/pull/5080))\n* Add gogo/protobuf to opentelemetry otlp data model ([@yurishkuro](https://github.com/yurishkuro) in [#5067](https://github.com/jaegertracing/jaeger/pull/5067))\n* Remove grpc-gateway dependency ([@yurishkuro](https://github.com/yurishkuro) in [#5060](https://github.com/jaegertracing/jaeger/pull/5060))\n* Add manual implementation of apiv3 http endpoints ([@yurishkuro](https://github.com/yurishkuro) in [#5054](https://github.com/jaegertracing/jaeger/pull/5054))\n* Allow specifying version for hotrod docker-compose ([@yurishkuro](https://github.com/yurishkuro) in [#5011](https://github.com/jaegertracing/jaeger/pull/5011))\n\n#### 👷 CI Improvements:\n\n* Publish go tip test report ([@albertteoh](https://github.com/albertteoh) in [#5082](https://github.com/jaegertracing/jaeger/pull/5082))\n* Upload test report ([@albertteoh](https://github.com/albertteoh) in [#5035](https://github.com/jaegertracing/jaeger/pull/5035))\n* Separate test report collection from the main target ([@yurishkuro](https://github.com/yurishkuro) in [#5061](https://github.com/jaegertracing/jaeger/pull/5061))\n* Bugfix: set pipefail when running unit tests to preserve exit code ([@yurishkuro](https://github.com/yurishkuro) in [#5057](https://github.com/jaegertracing/jaeger/pull/5057))\n* Regenerate thrift types and enable thrift check ([@yurishkuro](https://github.com/yurishkuro) in [#5039](https://github.com/jaegertracing/jaeger/pull/5039))\n* Regenerate hotrod proto ([@yurishkuro](https://github.com/yurishkuro) in [#5040](https://github.com/jaegertracing/jaeger/pull/5040))\n* Fix permission failed on checks-run ([@albertteoh](https://github.com/albertteoh) in [#5041](https://github.com/jaegertracing/jaeger/pull/5041))\n* Refactor protobuf types generation ([@yurishkuro](https://github.com/yurishkuro) in [#5037](https://github.com/jaegertracing/jaeger/pull/5037))\n* Publish test report ([@albertteoh](https://github.com/albertteoh) in [#5030](https://github.com/jaegertracing/jaeger/pull/5030))\n* Ci: simplify check-label workflow ([@EshaanAgg](https://github.com/EshaanAgg) in [#5033](https://github.com/jaegertracing/jaeger/pull/5033))\n* Fix goroutine leaks in several packages ([@yurishkuro](https://github.com/yurishkuro) in [#5026](https://github.com/jaegertracing/jaeger/pull/5026))\n* Add goleak check in more tests that do not fail ([@akagami-harsh](https://github.com/akagami-harsh) in [#5025](https://github.com/jaegertracing/jaeger/pull/5025))\n* Ci: add retry logic in the install go tip github action ([@akagami-harsh](https://github.com/akagami-harsh) in [#5022](https://github.com/jaegertracing/jaeger/pull/5022))\n* Move go tip installation into sub-action ([@yurishkuro](https://github.com/yurishkuro) in [#5020](https://github.com/jaegertracing/jaeger/pull/5020))\n* Add goleak check to packages with empty tests ([@yurishkuro](https://github.com/yurishkuro) in [#5017](https://github.com/jaegertracing/jaeger/pull/5017))\n* Add goleak check to cmd/agent/app/configmanager ([@yurishkuro](https://github.com/yurishkuro) in [#5015](https://github.com/jaegertracing/jaeger/pull/5015))\n* Feature: add goleak to check goroutine leak in tests ([@akagami-harsh](https://github.com/akagami-harsh) in [#5010](https://github.com/jaegertracing/jaeger/pull/5010))\n* Remove custom gocache location ([@yurishkuro](https://github.com/yurishkuro) in [#4995](https://github.com/jaegertracing/jaeger/pull/4995))\n\n1.52.0 (2023-12-05)\n-------------------\n\n### Backend Changes\n\n#### ✨ New Features:\n\n* Support Elasticsearch 8.x ([@pmuls99](https://github.com/pmuls99) in [#4829](https://github.com/jaegertracing/jaeger/pull/4829))\n* Make ArchiveTrace button auto-configurable ([@thecoons](https://github.com/thecoons) in [#4913](https://github.com/jaegertracing/jaeger/pull/4913))\n\n#### 🐞 Bug fixes, Minor Improvements:\n\n* [SPM] differentiate null from no error data ([@albertteoh](https://github.com/albertteoh) in [#4985](https://github.com/jaegertracing/jaeger/pull/4985))\n* Fix example/grafana-integration ([@angristan](https://github.com/angristan) in [#4980](https://github.com/jaegertracing/jaeger/pull/4980))\n* Fix (badger): add missing SamplingStoreFactory.CreateLock method ([@slayer321](https://github.com/slayer321) in [#4966](https://github.com/jaegertracing/jaeger/pull/4966))\n* Normalize metric names due to breaking change ([@albertteoh](https://github.com/albertteoh) in [#4957](https://github.com/jaegertracing/jaeger/pull/4957))\n* [kafka-consumer] add topic name as a tag to offset manager metrics ([@abliqo](https://github.com/abliqo) in [#4951](https://github.com/jaegertracing/jaeger/pull/4951))\n* Make UI placeholder more descriptive ([@yurishkuro](https://github.com/yurishkuro) in [#4937](https://github.com/jaegertracing/jaeger/pull/4937))\n* Remove google.golang.org/protobuf dependency from model & storage apis ([@akagami-harsh](https://github.com/akagami-harsh) in [#4917](https://github.com/jaegertracing/jaeger/pull/4917))\n* Read OTEL env vars for resource attributes ([@yurishkuro](https://github.com/yurishkuro) in [#4932](https://github.com/jaegertracing/jaeger/pull/4932))\n\n#### 🚧 Experimental Features:\n\n* Exp: rename jaeger-v2 binary to just jaeger ([@yurishkuro](https://github.com/yurishkuro) in [#4918](https://github.com/jaegertracing/jaeger/pull/4918))\n\n#### 👷 CI Improvements:\n\n* [CI]: improve kafka integration test self-sufficiency ([@RipulHandoo](https://github.com/RipulHandoo) in [#4989](https://github.com/jaegertracing/jaeger/pull/4989))\n* Separate all-in-one integration tests for v1 and v2 ([@yurishkuro](https://github.com/yurishkuro) in [#4968](https://github.com/jaegertracing/jaeger/pull/4968))\n* Collect code coverage from integration tests and upload to codecov ([@yurishkuro](https://github.com/yurishkuro) in [#4964](https://github.com/jaegertracing/jaeger/pull/4964))\n* [CI/ES] use default template priorities ([@yurishkuro](https://github.com/yurishkuro) in [#4962](https://github.com/jaegertracing/jaeger/pull/4962))\n* Unleash dependabot on docker files and add dependency review workflow ([@step-security-bot](https://github.com/step-security-bot) in [#4945](https://github.com/jaegertracing/jaeger/pull/4945))\n* Split unit-test workflow into tests and lint ([@MeenuyD](https://github.com/MeenuyD) in [#4933](https://github.com/jaegertracing/jaeger/pull/4933))\n* [CI]: harden github actions ([@step-security-bot](https://github.com/step-security-bot) in [#4923](https://github.com/jaegertracing/jaeger/pull/4923))\n* [CI]: build jaeger v2 image on main branch runs ([@yurishkuro](https://github.com/yurishkuro) in [#4920](https://github.com/jaegertracing/jaeger/pull/4920))\n* Exp: publish jaeger v2 image ([@yurishkuro](https://github.com/yurishkuro) in [#4919](https://github.com/jaegertracing/jaeger/pull/4919))\n* [CI]: set default to fix 'unbound variable' error on main ([@yurishkuro](https://github.com/yurishkuro) in [#4916](https://github.com/jaegertracing/jaeger/pull/4916))\n* [CI]: test jaeger-v2 as all-in-one in ci ([@yurishkuro](https://github.com/yurishkuro) in [#4890](https://github.com/jaegertracing/jaeger/pull/4890))\n* Fix release script broken by recent linting cleanup ([@yurishkuro](https://github.com/yurishkuro) in [#4915](https://github.com/jaegertracing/jaeger/pull/4915))\n\n### UI Changes\n\n* UI pinned to version [1.36.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1360-2023-12-05).\n\n1.51.0 (2023-11-04)\n-------------------\n\n### Backend Changes\n\n#### ✨ New Features:\n\n* Feat: add sampling store support to badger ([@slayer321](https://github.com/slayer321) in [#4834](https://github.com/jaegertracing/jaeger/pull/4834))\n* Feat: add span adjuster that moves some otel resource attributes to span.process ([@james-ryans](https://github.com/james-ryans) in [#4844](https://github.com/jaegertracing/jaeger/pull/4844))\n* Add product/file version in windows executables ([@ResamVi](https://github.com/ResamVi) in [#4811](https://github.com/jaegertracing/jaeger/pull/4811))\n\n#### 🐞 Bug fixes, Minor Improvements:\n\n* Fix dependency policy and add to security-insights.yml ([@jkowall](https://github.com/jkowall) in [#4907](https://github.com/jaegertracing/jaeger/pull/4907))\n* Add reload interval to otel server certificates ([@james-ryans](https://github.com/james-ryans) in [#4898](https://github.com/jaegertracing/jaeger/pull/4898))\n* Feat: add blackhole storage, for benchmarking ([@yurishkuro](https://github.com/yurishkuro) in [#4896](https://github.com/jaegertracing/jaeger/pull/4896))\n* Add otel resource detector to jaeger components ([@james-ryans](https://github.com/james-ryans) in [#4864](https://github.com/jaegertracing/jaeger/pull/4864))\n* Fix batchprocessor to set correct span format flags ([@k0zl](https://github.com/k0zl) in [#4796](https://github.com/jaegertracing/jaeger/pull/4796))\n* Expose collector ports in docker images ([@arunvelsriram](https://github.com/arunvelsriram) in [#4810](https://github.com/jaegertracing/jaeger/pull/4810))\n\n#### 🚧 Experimental Features:\n\n* Exp(jaeger-v2): simplify all-in-one configuration ([@yurishkuro](https://github.com/yurishkuro) in [#4875](https://github.com/jaegertracing/jaeger/pull/4875))\n* Exp: support primary and archive storage ([@yurishkuro](https://github.com/yurishkuro) in [#4873](https://github.com/jaegertracing/jaeger/pull/4873))\n* Feat(jaeger-v2): create default config for all-in-one ([@yurishkuro](https://github.com/yurishkuro) in [#4842](https://github.com/jaegertracing/jaeger/pull/4842))\n\n#### 👷 CI Improvements:\n\n* Ci: split the install-tools into test/build groups ([@MeenuyD](https://github.com/MeenuyD) in [#4878](https://github.com/jaegertracing/jaeger/pull/4878))\n* Simplify binary building in makefile ([@yurishkuro](https://github.com/yurishkuro) in [#4885](https://github.com/jaegertracing/jaeger/pull/4885))\n* Ci: pass variable instead of calling make build-xxx-debug ([@yurishkuro](https://github.com/yurishkuro) in [#4883](https://github.com/jaegertracing/jaeger/pull/4883))\n* Simplify makefile ([@yurishkuro](https://github.com/yurishkuro) in [#4882](https://github.com/jaegertracing/jaeger/pull/4882))\n* Test: add more linters ([@yurishkuro](https://github.com/yurishkuro) in [#4881](https://github.com/jaegertracing/jaeger/pull/4881))\n* Ci: enable linting of code in examples/ ([@yurishkuro](https://github.com/yurishkuro) in [#4880](https://github.com/jaegertracing/jaeger/pull/4880))\n* Ci: keep the ui asset's .gz file timestamps the same as the original file ([@yurishkuro](https://github.com/yurishkuro) in [#4879](https://github.com/jaegertracing/jaeger/pull/4879))\n* Add first pass at the security-insights.yml ([@jkowall](https://github.com/jkowall) in [#4872](https://github.com/jaegertracing/jaeger/pull/4872))\n* Create scorecard.yml for ossf implementation ([@jkowall](https://github.com/jkowall) in [#4870](https://github.com/jaegertracing/jaeger/pull/4870))\n* Add ci validation of shell scripts using shellcheck ([@akagami-harsh](https://github.com/akagami-harsh) in [#4826](https://github.com/jaegertracing/jaeger/pull/4826))\n* Chore: add dynamic loading bar functionality to release-notes.py ([@anshgoyalevil](https://github.com/anshgoyalevil) in [#4857](https://github.com/jaegertracing/jaeger/pull/4857))\n* Ci: add the label-check workflow to verify changelog labels on each pr ([@anshgoyalevil](https://github.com/anshgoyalevil) in [#4847](https://github.com/jaegertracing/jaeger/pull/4847))\n* Ci(hotrod): print hotrod container logs in case of test failure ([@yurishkuro](https://github.com/yurishkuro) in [#4845](https://github.com/jaegertracing/jaeger/pull/4845))\n* Ci: drop -v from ci unit tests to make failures easier to see ([@yurishkuro](https://github.com/yurishkuro) in [#4839](https://github.com/jaegertracing/jaeger/pull/4839))\n* Use commit hash as image label when building & integration-testing ([@yurishkuro](https://github.com/yurishkuro) in [#4824](https://github.com/jaegertracing/jaeger/pull/4824))\n* Clean-up some linter warnings in build scripts ([@yurishkuro](https://github.com/yurishkuro) in [#4823](https://github.com/jaegertracing/jaeger/pull/4823))\n* Fix build-all-in-one-image script ([@albertteoh](https://github.com/albertteoh) in [#4819](https://github.com/jaegertracing/jaeger/pull/4819))\n* [ci-release] improve release workflow for manual runs ([@yurishkuro](https://github.com/yurishkuro) in [#4818](https://github.com/jaegertracing/jaeger/pull/4818))\n* Add --force to docker commands ([@albertteoh](https://github.com/albertteoh) in [#4820](https://github.com/jaegertracing/jaeger/pull/4820))\n* Use setup-node.js for publish release ([@albertteoh](https://github.com/albertteoh) in [#4816](https://github.com/jaegertracing/jaeger/pull/4816))\n* Clean up ci scripts and prune docker images between builds ([@yurishkuro](https://github.com/yurishkuro) in [#4815](https://github.com/jaegertracing/jaeger/pull/4815))\n* Clean-up & fortify ci-release ([@yurishkuro](https://github.com/yurishkuro) in [#4813](https://github.com/jaegertracing/jaeger/pull/4813))\n\n### UI Changes\n\n* UI pinned to version [1.35.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1350-2023-11-02).\n\n1.50.0 (2023-10-06)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [sampling] Remove support for SAMPLING_TYPE env var and 'static' value ([@yurishkuro](https://github.com/yurishkuro) in [#4735](https://github.com/jaegertracing/jaeger/pull/4735))\n* Use non-root user in built containers ([@nikzayn](https://github.com/nikzayn) in [#4783](https://github.com/jaegertracing/jaeger/pull/4783)) - this change may cause issues with existing installations using Badger storage, because the existing files would be owned by a different user and would not be writeable after Jaeger upgrade. The workaround is to manually chown the files to the new user (uid=10001).\n\n#### New Features\n\n* Add cassandra schema compaction window configuration ([@sameersecond](https://github.com/sameersecond) in [#4767](https://github.com/jaegertracing/jaeger/pull/4767))\n* Add jaeger-v2 single binary based on otel collector ([@yurishkuro](https://github.com/yurishkuro) in [#4766](https://github.com/jaegertracing/jaeger/pull/4766))\n* [kafka-consumer] Consumer metrics should have a tag with topic name ([@abliqo](https://github.com/abliqo) in [#4778](https://github.com/jaegertracing/jaeger/pull/4778))\n* Support http proxy env variables ([@pavolloffay](https://github.com/pavolloffay) in [#4769](https://github.com/jaegertracing/jaeger/pull/4769))\n* Support reloading es client's password from file ([@haanhvu](https://github.com/haanhvu) in [#4342](https://github.com/jaegertracing/jaeger/pull/4342))\n\n#### Bug fixes, Minor Improvements\n\n* Fix jaegerqueryreqsfailing alert rule missing 'operation' in query ([@james-ryans](https://github.com/james-ryans) in [#4797](https://github.com/jaegertracing/jaeger/pull/4797))\n* Add e2e test for sampling storage ([@slayer321](https://github.com/slayer321) in [#4772](https://github.com/jaegertracing/jaeger/pull/4772))\n* [tests] Simplify cassandra e2e test cleanup ([@yurishkuro](https://github.com/yurishkuro) in [#4794](https://github.com/jaegertracing/jaeger/pull/4794))\n* [tests] Fix failing e2e test for cassandra storage ([@slayer321](https://github.com/slayer321) in [#4776](https://github.com/jaegertracing/jaeger/pull/4776))\n* Remove unneeded references to opentracing ([@yurishkuro](https://github.com/yurishkuro) in [#4790](https://github.com/jaegertracing/jaeger/pull/4790))\n* Use non-root user in built containers ([@nikzayn](https://github.com/nikzayn) in [#4783](https://github.com/jaegertracing/jaeger/pull/4783))\n* Run all integration tests against cassandra ([@yurishkuro](https://github.com/yurishkuro) in [#4773](https://github.com/jaegertracing/jaeger/pull/4773))\n* [hotrod] Log driver locations as json to demo respective ui capability ([@yurishkuro](https://github.com/yurishkuro) in [#4765](https://github.com/jaegertracing/jaeger/pull/4765))\n* Replace python script with tracegen ([@albertteoh](https://github.com/albertteoh) in [#4753](https://github.com/jaegertracing/jaeger/pull/4753))\n* [fix] Close elasticsearch client properly ([@Lauquik](https://github.com/Lauquik) in [#4754](https://github.com/jaegertracing/jaeger/pull/4754))\n* Add deprecation warning to jaeger-agent ([@yurishkuro](https://github.com/yurishkuro) in [#4749](https://github.com/jaegertracing/jaeger/pull/4749))\n* Deprecate grpc-storage-plugin sidecar model ([@yurishkuro](https://github.com/yurishkuro) in [#4744](https://github.com/jaegertracing/jaeger/pull/4744))\n* Upgrade query api v3 to official opentelemetry format ([@yurishkuro](https://github.com/yurishkuro) in [#4742](https://github.com/jaegertracing/jaeger/pull/4742))\n* [SPM] Deprecate support for spanmetrics processor naming convention ([@yurishkuro](https://github.com/yurishkuro) in [#4741](https://github.com/jaegertracing/jaeger/pull/4741))\n* Deprecate expvar metrics backend ([@yurishkuro](https://github.com/yurishkuro) in [#4740](https://github.com/jaegertracing/jaeger/pull/4740))\n* Fix flaky testgetroundtripper* tests ([@albertteoh](https://github.com/albertteoh) in [#4738](https://github.com/jaegertracing/jaeger/pull/4738))\n\n### UI Changes\n\n* UI pinned to version [1.34.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1340-2023-10-04).\n\n1.49.0 (2023-09-07)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [hotrod] Make OTLP the default exporter in HotROD ([@yurishkuro](https://github.com/yurishkuro) in [#4698](https://github.com/jaegertracing/jaeger/pull/4698))\n* [SPM] Support spanmetrics connector by default ([@albertteoh](https://github.com/albertteoh) in [#4704](https://github.com/jaegertracing/jaeger/pull/4704))\n* [tracegen] Stop supporting -trace-exporter=jaeger ([@yurishkuro](https://github.com/yurishkuro) in [#4717](https://github.com/jaegertracing/jaeger/pull/4717))\n* [hotrod] Stop supporting -otel-exporter=jaeger ([@yurishkuro](https://github.com/yurishkuro) in [#4719](https://github.com/jaegertracing/jaeger/pull/4719))\n* [hotrod] Metrics endpoints moved from route service (:8083) to frontend service (:8080) ([@yurishkuro](https://github.com/yurishkuro) in [#4720](https://github.com/jaegertracing/jaeger/pull/4720))\n\n#### Bug fixes, Minor Improvements\n\n* Allow disabling brearer token override from request in metrics store ([@pavolloffay](https://github.com/pavolloffay) in [#4726](https://github.com/jaegertracing/jaeger/pull/4726))\n* Add the enable tracing opt-in flag ([@albertteoh](https://github.com/albertteoh) in [#4685](https://github.com/jaegertracing/jaeger/pull/4685))\n* [tracegen] Add build info during compilation ([@yurishkuro](https://github.com/yurishkuro) in [#4727](https://github.com/jaegertracing/jaeger/pull/4727))\n* Log version/build info on startup ([@yurishkuro](https://github.com/yurishkuro) in [#4723](https://github.com/jaegertracing/jaeger/pull/4723))\n* [zipkin] Replace zipkin exporter from jaeger sdk with otel zipkin exp ([@afzal442](https://github.com/afzal442) in [#4674](https://github.com/jaegertracing/jaeger/pull/4674))\n\n### UI Changes\n\n* UI pinned to version [1.33.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1330-2023-08-06).\n\n1.48.0 (2023-08-15)\n-------------------\n\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* [fix] Disable tracing of OTLP Receiver ([@yurishkuro](https://github.com/yurishkuro) in [#4662](https://github.com/jaegertracing/jaeger/pull/4662))\n* [hotrod/observer_test] Switch to OpenTelemetry ([@afzal442](https://github.com/afzal442) in [#4635](https://github.com/jaegertracing/jaeger/pull/4635))\n* [memstore-plugin]Switch to OpenTelemetry SDK ([@afzal442](https://github.com/afzal442) in [#4643](https://github.com/jaegertracing/jaeger/pull/4643))\n* [tracegen] Allow to control cardinality of attribute keys ([@yurishkuro](https://github.com/yurishkuro) in [#4634](https://github.com/jaegertracing/jaeger/pull/4634))\n* Replace OT const wth OTEL trace.span for zipkin comp ([@afzal442](https://github.com/afzal442) in [#4625](https://github.com/jaegertracing/jaeger/pull/4625))\n* Replace OpenTracing instrumentation with OpenTelemetry in grpc storage plugin ([@afzal442](https://github.com/afzal442) in [#4611](https://github.com/jaegertracing/jaeger/pull/4611))\n* Replace OT trace with `otel trace` spans type to span model ([@afzal442](https://github.com/afzal442) in [#4614](https://github.com/jaegertracing/jaeger/pull/4614))\n* Replace cassandra-spanstore tracing instrumentation with`OTEL` ([@afzal442](https://github.com/afzal442) in [#4599](https://github.com/jaegertracing/jaeger/pull/4599))\n* Replace es-spanstore tracing instrumentation with OpenTelemetry ([@afzal442](https://github.com/afzal442) in [#4596](https://github.com/jaegertracing/jaeger/pull/4596))\n* Replace metricsstore/reader tracing instrumentation with OpenTelemetry ([@afzal442](https://github.com/afzal442) in [#4595](https://github.com/jaegertracing/jaeger/pull/4595))\n* Replace Jaeger SDK with OTEL SDK + OT Bridge ([@afzal442](https://github.com/afzal442) in [#4574](https://github.com/jaegertracing/jaeger/pull/4574))\n* [kafka-consumer] Ingester should use topic name from actual Kafka consumer instead of configuration ([@abliqo](https://github.com/abliqo) in [#4593](https://github.com/jaegertracing/jaeger/pull/4593))\n* Enable CORS settings on OTLP HTTP endpoint ([@pmuls99](https://github.com/pmuls99) in [#4586](https://github.com/jaegertracing/jaeger/pull/4586))\n* [hotrod] Return trace ID via traceresponse header ([@yurishkuro](https://github.com/yurishkuro) in [#4584](https://github.com/jaegertracing/jaeger/pull/4584))\n* [hotrod] Remove most references to OpenTracing ([@yurishkuro](https://github.com/yurishkuro) in [#4585](https://github.com/jaegertracing/jaeger/pull/4585))\n* [hotrod] Validate user input to avoid security warnings from code scanning ([@yurishkuro](https://github.com/yurishkuro) in [#4583](https://github.com/jaegertracing/jaeger/pull/4583))\n* [hotrod] Upgrade HotROD to use OpenTelemetry instrumentation ([@afzal442](https://github.com/afzal442) in [#4548](https://github.com/jaegertracing/jaeger/pull/4548))\n* [kafka-consumer] Use wait group to ensure goroutine is finished before returning from Close ([@kennyaz](https://github.com/kennyaz) in [#4582](https://github.com/jaegertracing/jaeger/pull/4582))\n* [tracegen] Enable BlockOnQueueFull in OTel SDK to avoid dropped spans ([@haanhvu](https://github.com/haanhvu) in [#4578](https://github.com/jaegertracing/jaeger/pull/4578))\n* [hotrod] Handle both OT and OTEL baggage ([@yurishkuro](https://github.com/yurishkuro) in [#4572](https://github.com/jaegertracing/jaeger/pull/4572))\n\n### UI Changes\n\n* UI pinned to version [1.32.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1320-2023-08-14).\n\n1.47.0 (2023-07-05)\n-------------------\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* [SPM] Due to a breaking change in OpenTelemetry's prometheus exporter ([details](https://github.com/open-telemetry/opentelemetry-collector-contrib/releases/tag/v0.80.0))\n  metric names will no longer be normalized by default, meaning that the expected metric names would be `calls` and\n  `duration_*`. Backwards compatibility with older OpenTelemetry Collector versions can be achieved through the following flags:\n  * `prometheus.query.normalize-calls`: If true, normalizes the \"calls\" metric name. e.g. \"calls_total\".\n  * `prometheus.query.normalize-duration`: If true, normalizes the \"duration\" metric name to include the duration units. e.g. \"duration_milliseconds_bucket\".\n\n#### New Features\n* [Cassandra] Add Configuration.Close() to ensure TLS cert watcher is closed ([@kennyaz](https://github.com/kennyaz) in [#4515](https://github.com/jaegertracing/jaeger/pull/4515))\n* Add *.kerberos.disable-fast-negotiation option to Kafka consumer ([@pmuls99](https://github.com/pmuls99) in [#4520](https://github.com/jaegertracing/jaeger/pull/4520))\n* Support Prometheus normalization for specific metrics related to OpenTelemetry compatibility ([@albertteoh](https://github.com/albertteoh) in [#4555](https://github.com/jaegertracing/jaeger/pull/4555))\n\n#### Bug fixes, Minor Improvements\n\n* Add readme for memstore plugin ([@yurishkuro](https://github.com/yurishkuro) in [283bdd9](https://github.com/jaegertracing/jaeger/commit/283bdd93cbb4a467842625d8eb320722fcb83494))\n* Pass a wrapper instead of `opentracing.Tracer` to ease migration to OTEL in the future [part 1] ([@afzalbin64](https://github.com/afzalbin64) in [#4529](https://github.com/jaegertracing/jaeger/pull/4529))\n* [hotROD] Add OTEL instrumentation to customer svc ([@afzal442](https://github.com/afzal442) in [#4559](https://github.com/jaegertracing/jaeger/pull/4559))\n* [hotROD] Replace gRPC instrumentation with OTEL ([@afzal442](https://github.com/afzal442) in [#4558](https://github.com/jaegertracing/jaeger/pull/4558))\n* [hotROD]: Upgrade `redis` service to use native OTEL instrumentation ([@afzal442](https://github.com/afzal442) in [#4533](https://github.com/jaegertracing/jaeger/pull/4533))\n* [hotROD] Fix OTEL logging in HotRod example ([@albertteoh](https://github.com/albertteoh) in [#4556](https://github.com/jaegertracing/jaeger/pull/4556))\n* [hotrod] Reduce span exporter's batch timeout to let the spans be exported sooner ([@GLVSKiriti](https://github.com/GLVSKiriti) in [#4518](https://github.com/jaegertracing/jaeger/pull/4518))\n* [tracegen] Add options to generate more spans and attributes for additional use cases ([@yurishkuro](https://github.com/yurishkuro) in [#4535](https://github.com/jaegertracing/jaeger/pull/4535))\n* Build improvement to rebuild jaeger-ui if the tree does not match any tag ([@bobrik](https://github.com/bobrik) in [#4553](https://github.com/jaegertracing/jaeger/pull/4553))\n* [Test] Fixed a race condition causing unit test failure by changing the logging  ([@yurishkuro](https://github.com/yurishkuro) in [#4546](https://github.com/jaegertracing/jaeger/pull/4546)) resolves [#4497](https://github.com/jaegertracing/jaeger/issues/4497)\n\n\n### UI Changes\n\n* UI pinned to version [1.31.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1310-2023-07-05).\n\n\n1.46.0 (2023-06-05)\n-------------------\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\nOTLP receiver is now enabled by default ([#4494](https://github.com/jaegertracing/jaeger/pull/4494)). This change follows the Jaeger's strategic direction of aligning closely with the OpenTelemetry project. This may cause port conflicts if  `jaeger-collector` is depoyed in host network namespace. The original `--collector.otlp.enabled` option is still available and MUST be set to `false` if OTLP receiver is not desired.\n\n#### New Features\n\n* Make OTLP receiver enabled by default ([@yurishkuro](https://github.com/yurishkuro) in [#4494](https://github.com/jaegertracing/jaeger/pull/4494))\n* [SPM] Add support for OpenTelemetry SpanMetrics Connector ([@albertteoh](https://github.com/albertteoh) in [#4452](https://github.com/jaegertracing/jaeger/pull/4452)). See [Migration README](https://github.com/jaegertracing/jaeger/blob/main/docker-compose/monitor/README.md#migrating).\n\n#### Bug fixes, Minor Improvements\n\n* Log processor error in Kafka consumer ([@pavolloffay](https://github.com/pavolloffay) in [#4399](https://github.com/jaegertracing/jaeger/pull/4399))\n* [bug] Remove TerminateAfter from Elasticsearch/Opensearch query resulting in incomplete span count/list ([@Jakob3xD](https://github.com/Jakob3xD) in [#4336](https://github.com/jaegertracing/jaeger/pull/4336))\n* [agent] Use RawConn.Control to get fd instead of Fd() to prevent deadlock on shutdown ([@ChenX1993](https://github.com/ChenX1993) in [#4449](https://github.com/jaegertracing/jaeger/pull/4449))\n* [SPM] Fix docker compose command ([@tqi-raurora](https://github.com/tqi-raurora) in [#4444](https://github.com/jaegertracing/jaeger/pull/4444))\n\n#### Maintenance\n\n* [test] Fix flaky test - TestSpanProcessorWithOnDroppedSpanOption ([@yurishkuro](https://github.com/yurishkuro) in [#4489](https://github.com/jaegertracing/jaeger/pull/4489))\n* [ci] Skip debug builds when not making a release ([@psk001](https://github.com/psk001) in [#4496](https://github.com/jaegertracing/jaeger/pull/4496))\n* Fix some function comments ([@cuishuang](https://github.com/cuishuang) in [#4410](https://github.com/jaegertracing/jaeger/pull/4410))\n* Increase dependabot open-pull-requests-limit=10 ([@yurishkuro](https://github.com/yurishkuro) in [04548fc](https://github.com/jaegertracing/jaeger/commit/04548fc339689f970da2de36b964fd3abfca41c2))\n* Add jkowall as release manger for July ([@jkowall](https://github.com/jkowall) in [#4446](https://github.com/jaegertracing/jaeger/pull/4446))\n* Fix versions in release schedule ([@yurishkuro](https://github.com/yurishkuro) in [8a9d13a](https://github.com/jaegertracing/jaeger/commit/8a9d13a31b477707cec73a9b7bf6242b27cec0ea))\n\n### UI Changes\n\n* UI pinned to version [1.30.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1300-2023-06-05).\n\n\n1.45.0 (2023-05-03)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* Add HTTP post port mapping to docker command ([@albertteoh](https://github.com/albertteoh) in [#4407](https://github.com/jaegertracing/jaeger/pull/4407))\n* Simplify ES config and factory ([@yurishkuro](https://github.com/yurishkuro) in [#4396](https://github.com/jaegertracing/jaeger/pull/4396))\n* Add otlp-grpc for tracegen's trace-exporter ([@boysusu](https://github.com/boysusu) in [#4374](https://github.com/jaegertracing/jaeger/pull/4374))\n* Allow follows-from reference as a parent span id ([@kubarydz](https://github.com/kubarydz) in [#4382](https://github.com/jaegertracing/jaeger/pull/4382))\n* Expose drop span hook as an option in Collector SpanProcessor ([@ChenX1993](https://github.com/ChenX1993) in [#4387](https://github.com/jaegertracing/jaeger/pull/4387))\n\n### UI Changes\n\n* UI pinned to version [1.29.1](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1291-2023-05-03).\n\n\n1.44.0 (2023-04-10)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* Store span warnings as tags in Cassandra ([@utsavoza](https://github.com/utsavoza) in [#4313](https://github.com/jaegertracing/jaeger/pull/4313))\n* Add Keep-Alive flag for Zipkin HTTP server ([@topjung3](https://github.com/topjung3) in [#4366](https://github.com/jaegertracing/jaeger/pull/4366))\n* Log access to static assets; remove favicon test ([@yurishkuro](https://github.com/yurishkuro) in [#4302](https://github.com/jaegertracing/jaeger/pull/4302))\n\n### UI Changes\n\n* UI pinned to version [1.29.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1290-2023-04-10).\n\n1.43.0 (2023-03-15)\n-------------------\n### Backend Changes\n\n#### New Features\n\n* Support secure OTLP exporter config for hotrod ([@graphaelli](https://github.com/graphaelli) in [#4231](https://github.com/jaegertracing/jaeger/pull/4231))\n* Jaeger YugabyteDB(YCQL) support ([@HarshDaryani896](https://github.com/HarshDaryani896) in [#4220](https://github.com/jaegertracing/jaeger/pull/4220))\n\n#### Bug fixes, Minor Improvements\n\n* Replace pkg/multierror with standard errors.Join ([@ClementRepo](https://github.com/ClementRepo) in [#4293](https://github.com/jaegertracing/jaeger/pull/4293))\n* Remove pkg/multicloser ([@yurishkuro](https://github.com/yurishkuro) in [#4291](https://github.com/jaegertracing/jaeger/pull/4291))\n* Refactor build linux artifacts only for PR ([@Eileen-Yu](https://github.com/Eileen-Yu) in [#4286](https://github.com/jaegertracing/jaeger/pull/4286))\n* Speed-up CI by using published UI artifacts ([@shubbham1219](https://github.com/shubbham1219) in [#4251](https://github.com/jaegertracing/jaeger/pull/4251))\n* Update Go version to 1.20 ([@SaarthakMaini](https://github.com/SaarthakMaini) in [#4206](https://github.com/jaegertracing/jaeger/pull/4206))\n* Use http.MethodGet instead of \"GET\" ([@my-git9](https://github.com/my-git9) in [#4248](https://github.com/jaegertracing/jaeger/pull/4248))\n* Updating all-in-one path ([@bigfleet](https://github.com/bigfleet) in [#4234](https://github.com/jaegertracing/jaeger/pull/4234))\n* Migrate the use of fsnotify to fswatcher in cert_watcher.go ([@haanhvu](https://github.com/haanhvu) in [#4232](https://github.com/jaegertracing/jaeger/pull/4232))\n* Restore baggage support in HotROD 🚗 ([@yurishkuro](https://github.com/yurishkuro) in [#4225](https://github.com/jaegertracing/jaeger/pull/4225))\n\n### UI Changes\n\n* UI pinned to version [1.28.1](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1281-2023-03-15).\n\n1.42.0 (2023-02-05)\n-------------------\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* HotROD 🚗 application is switched from Jaeger SDK to OpenTelemetry SDK ([@yurishkuro](https://github.com/yurishkuro) in [#4187](https://github.com/jaegertracing/jaeger/pull/4187)). Some environment variables previously accepted are no longer recognized. See PR for details.\n  * Map old env vars from Jaeger SDK to OTel SDK vars ([@yurishkuro](https://github.com/yurishkuro) in [#4200](https://github.com/jaegertracing/jaeger/pull/4200))\n  * Use patched version of github.com/opentracing-contrib/go-grpc in HotROD ([@yurishkuro](https://github.com/yurishkuro) in [#4210](https://github.com/jaegertracing/jaeger/pull/4210))\n* `tracegen` utility is switched from Jaeger SDK to OpenTelemetry SDK ([@yurishkuro](https://github.com/yurishkuro) in [#4189](https://github.com/jaegertracing/jaeger/pull/4189)). Some environment variables previously accepted are no longer recognized. See PR for details.\n\n#### New Features\n\n* Add CLI flags for controlling HTTP server timeouts ([@yurishkuro](https://github.com/yurishkuro) in [#4167](https://github.com/jaegertracing/jaeger/pull/4167))\n* Watch directories for certificate hot-reload ([@tsaarni](https://github.com/tsaarni) in [#4159](https://github.com/jaegertracing/jaeger/pull/4159))\n* Support tenant header propagation in query service and grpc-plugin ([@pavolloffay](https://github.com/pavolloffay) in [#4151](https://github.com/jaegertracing/jaeger/pull/4151))\n\n\n#### Bug fixes, Minor Improvements\n\n* Replace Thrift-gen with Proto-gen types for sampling strategies ([@yurishkuro](https://github.com/yurishkuro) in [#4181](https://github.com/jaegertracing/jaeger/pull/4181))\n* Use Protobuf-based JSON output for sampling strategies ([@yurishkuro](https://github.com/yurishkuro) in [#4173](https://github.com/jaegertracing/jaeger/pull/4173))\n* [tests]: Use `t.Setenv` to set env vars in tests ([@Juneezee](https://github.com/Juneezee) in [#4169](https://github.com/jaegertracing/jaeger/pull/4169))\n* ci(lint): bump golangci-lint to v1.50.1 ([@mmorel-35](https://github.com/mmorel-35) in [#4195](https://github.com/jaegertracing/jaeger/pull/4195))\n* Convert token propagation integration test to plain unit test ([@yurishkuro](https://github.com/yurishkuro) in [#4162](https://github.com/jaegertracing/jaeger/pull/4162))\n* Refactor scripts/es-integration-test.sh ([@yurishkuro](https://github.com/yurishkuro) in [#4161](https://github.com/jaegertracing/jaeger/pull/4161))\n* Fix \"index out of range\" when receiving a dual client/server Zipkin span ([@yurishkuro](https://github.com/yurishkuro) in [#4160](https://github.com/jaegertracing/jaeger/pull/4160))\n\n### UI Changes\n\n* No changes.\n\n1.41.0 (2023-01-04)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* Remove global platform arg in cassandra schema dockerfile ([@jkandasa](https://github.com/jkandasa) in [#4123](https://github.com/jaegertracing/jaeger/pull/4123))\n* Add multi arch support to cassandra-schema container ([@jkandasa](https://github.com/jkandasa) in [#4122](https://github.com/jaegertracing/jaeger/pull/4122))\n\n### UI\n\n* No changes.\n\n1.40.0 (2022-12-07)\n-------------------\n### Backend Changes\n\n#### New Features\n\n* Release signing ([@jkowall](https://github.com/jkowall) in [#4033](https://github.com/jaegertracing/jaeger/pull/4033))\n\n#### Bug fixes, Minor Improvements\n\n* Fix cassandra schema scripts to be able to run from a remote node ([@yurishkuro](https://github.com/yurishkuro) in [#4087](https://github.com/jaegertracing/jaeger/pull/4087))\n* Catch panic from Prometheus client on invalid label strings ([@yurishkuro](https://github.com/yurishkuro) in [#4051](https://github.com/jaegertracing/jaeger/pull/4051))\n* Span tags of type int64 may lose precision ([@shubbham1215](https://github.com/shubbham1215) in [#4034](https://github.com/jaegertracing/jaeger/pull/4034))\n\n### UI\n\n* UI pinned to version [1.27.3](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1273-2022-12-07).\n\n\n1.39.0 (2022-11-01)\n-------------------\n### Backend Changes\n\n#### New Features\n\n* Add support for OpenSearch 2.x ([@gaurav-05](https://github.com/gaurav-05) in [#3966](https://github.com/jaegertracing/jaeger/pull/3966))\n\n#### Bug fixes, Minor Improvements\n\n* Pin SBOM action to commit ([@yurishkuro](https://github.com/yurishkuro) in [bb49249](https://github.com/jaegertracing/jaeger/commit/bb492490594c9d9321ed9242862ac2a8864ff771))\n* Remove auth requirement on monitor demo ([@joe-elliott](https://github.com/joe-elliott) in [#4005](https://github.com/jaegertracing/jaeger/pull/4005))\n* Increase sleep time when waiting for ES/OS backend ([@yurishkuro](https://github.com/yurishkuro) in [b9805f7](https://github.com/jaegertracing/jaeger/commit/b9805f7bc075224cfab37abab9df24ca51f38683))\n* Fix CVE-2022-32149 for gotlang.org/x/text package ([@mehta-ankit](https://github.com/mehta-ankit) in [#3992](https://github.com/jaegertracing/jaeger/pull/3992))\n* Expose otel configured thrift http port ([@albertteoh](https://github.com/albertteoh) in [#3986](https://github.com/jaegertracing/jaeger/pull/3986))\n* Adding anchore for SBOM signing during release ([@jkowall](https://github.com/jkowall) in [#3987](https://github.com/jaegertracing/jaeger/pull/3987))\n* Bump sarama to 1.33.0 ([@pavolloffay](https://github.com/pavolloffay) in [#3983](https://github.com/jaegertracing/jaeger/pull/3983))\n* Add note on jaeger grpc storage compliance ([@arajkumar](https://github.com/arajkumar) in [#3985](https://github.com/jaegertracing/jaeger/pull/3985))\n* Added link to FOSSA and Artifact Hub to README ([@jkowall](https://github.com/jkowall) in [#3964](https://github.com/jaegertracing/jaeger/pull/3964))\n* Add grafana container to monitor docker-compose ([@albertteoh](https://github.com/albertteoh) in [#3955](https://github.com/jaegertracing/jaeger/pull/3955))\n* Expose storage integration helpers as go pkg ([@arajkumar](https://github.com/arajkumar) in [#3944](https://github.com/jaegertracing/jaeger/pull/3944))\n\n### UI\n\n* UI pinned to version [1.27.2](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1272-2022-11-01).\n\n1.38.1 (2022-10-04)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* Bump github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3939](https://github.com/jaegertracing/jaeger/pull/3939))\n* Bump github.com/apache/thrift from 0.16.0 to 0.17.0 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3936](https://github.com/jaegertracing/jaeger/pull/3936))\n* Bump github.com/hashicorp/go-hclog from 1.2.2 to 1.3.1 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3934](https://github.com/jaegertracing/jaeger/pull/3934))\n* Bump go.opentelemetry.io/otel from 1.9.0 to 1.10.0 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3932](https://github.com/jaegertracing/jaeger/pull/3932))\n* Bump github.com/hashicorp/go-plugin from 1.4.4 to 1.4.5 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3930](https://github.com/jaegertracing/jaeger/pull/3930))\n* Bump github.com/spf13/viper from 1.12.0 to 1.13.0 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3931](https://github.com/jaegertracing/jaeger/pull/3931))\n* Bump OTEL dependencies => v0.60.0 and grpc => v1.49.0 ([@yurishkuro](https://github.com/yurishkuro) in [#3928](https://github.com/jaegertracing/jaeger/pull/3928))\n* Bump golang.org/x/net to 2022-09-26 ([@yurishkuro](https://github.com/yurishkuro) in [#3927](https://github.com/jaegertracing/jaeger/pull/3927))\n* Bump codecov/codecov-action from 3.1.0 to 3.1.1 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3917](https://github.com/jaegertracing/jaeger/pull/3917))\n\n### UI Changes\n\n* UI pinned to version [1.27.1](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1271-2022-10-04) to bump dependencies.\n\n\n1.38.0 (2022-09-16)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* fix: jaeger-agent sampling endpoint returns backwards incompatible JSON ([@vprithvi](https://github.com/vprithvi) in [#3897](https://github.com/jaegertracing/jaeger/pull/3897))\n* fix: streaming span writer is not working in grpc based remote storage plugin ([@arajkumar](https://github.com/arajkumar) in [#3887](https://github.com/jaegertracing/jaeger/pull/3887))\n* Fix race condition when adding collector tags ([@yurishkuro](https://github.com/yurishkuro) in [#3886](https://github.com/jaegertracing/jaeger/pull/3886))\n* Change build info date to commit timestamp ([@TripleDogDare](https://github.com/TripleDogDare) in [#3876](https://github.com/jaegertracing/jaeger/pull/3876))\n* Add 🚗 ([@yurishkuro](https://github.com/yurishkuro) in [55a8ca9](https://github.com/jaegertracing/jaeger/commit/55a8ca97e3772579b395ffbe4b937a4f5993b008))\n* Add AdditionalDialOptions to ConnBuilder ([@vprithvi](https://github.com/vprithvi) in [#3865](https://github.com/jaegertracing/jaeger/pull/3865))\n* Add sample docker-compose configuration using Kafka ([@yurishkuro](https://github.com/yurishkuro) in [7006e9f](https://github.com/jaegertracing/jaeger/commit/7006e9fe50c8467ad6b84f2072a3cf136bfbe4ec))\n\n### UI Changes\n\n* UI pinned to version [1.27.0 - see the changelog](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1270-2022-09-15).\n\n1.37.0 (2022-08-03)\n-------------------\n### Backend Changes\n\n* Add remote-storage service ([@yurishkuro](https://github.com/yurishkuro) in [#3836](https://github.com/jaegertracing/jaeger/pull/3836))\n\n#### Bug fixes, Minor Improvements\n\n* Fix ingester panic when span.process=nil ([@locmai](https://github.com/locmai) in [#3819](https://github.com/jaegertracing/jaeger/pull/3819))\n* Added windows zip file generation ([@adhithyasrinivasan](https://github.com/adhithyasrinivasan) in [#3817](https://github.com/jaegertracing/jaeger/pull/3817))\n* Refactor gRPC storage plugin for better composability ([@yurishkuro](https://github.com/yurishkuro) in [#3833](https://github.com/jaegertracing/jaeger/pull/3833))\n\n### UI Changes\n\n* UI pinned to version [1.26.0 - see the changelog](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1260-2022-08-03).\n\n1.36.0 (2022-07-05)\n-------------------\n### Backend Changes\n\n#### New Features\n* Add flag to enable span size metrics reporting ([@ymtdzzz](https://github.com/ymtdzzz) in [#3782](https://github.com/jaegertracing/jaeger/pull/3782))\n  * Span size metrics are enabled via the `--collector.enable-span-size-metrics` flag (even if `--collector.queue-size-memory` is disabled).\n* Add multi-tenancy support ([@esnible](https://github.com/esnible) in [#3688](https://github.com/jaegertracing/jaeger/pull/3688))\n  * Enabled when `--multi_tenancy.enabled=true` is passed to the collector.\n  * The header carrying the tenants can be specified via the `--multi_tenancy.header` flag, which defaults to `x-tenant`.\n  * The list of allowed tenants can be set via the `--multi_tenancy.tenants` flag, which defaults to an unrestricted list of tenants.\n\n#### Bug fixes, Minor Improvements\n* Introduce ParentReference adjuster ([@bobrik](https://github.com/bobrik) in [#3786](https://github.com/jaegertracing/jaeger/pull/3786))\n* Ignore the problem of self-reported spans when multi-tenant enabled ([@esnible](https://github.com/esnible) in [#3787](https://github.com/jaegertracing/jaeger/pull/3787))\n* Copy expvar metrics implementation from jaeger-lib ([@yurishkuro](https://github.com/yurishkuro) in [#3772](https://github.com/jaegertracing/jaeger/pull/3772))\n* Copy Prometheus metrics implementation from jaeger-lib ([@yurishkuro](https://github.com/yurishkuro) in [#3771](https://github.com/jaegertracing/jaeger/pull/3771))\n* Copy metrics API from jaeger-lib ([@yurishkuro](https://github.com/yurishkuro) in [#3767](https://github.com/jaegertracing/jaeger/pull/3767))\n* Use file move instead of overwriting content ([@yurishkuro](https://github.com/yurishkuro) in [#3726](https://github.com/jaegertracing/jaeger/pull/3726))\n* Refactor tenancy checking from gRPC to gRPC batch consumer ([@esnible](https://github.com/esnible) in [#3718](https://github.com/jaegertracing/jaeger/pull/3718))\n* Fix ineffectual `--skip-dependencies` flag in es-rollover ([@frzifus](https://github.com/frzifus) in [#3724](https://github.com/jaegertracing/jaeger/pull/3724))\n* Fix custom gogo codec to allow OTLP data ([@yurishkuro](https://github.com/yurishkuro) in [#3719](https://github.com/jaegertracing/jaeger/pull/3719))\n\n### UI Changes\n\n* UI pinned to version [1.25.0 - see the changelog](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1250-2022-07-03).\n\n1.35.2 (2022-06-15)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n* fix: dependency flag not recognized ([@frzifus](https://github.com/frzifus) in [#3724](https://github.com/jaegertracing/jaeger/pull/3724))\n\n1.35.1 (2022-06-01)\n-------------------\n### Backend Changes\n\n#### Bug fixes, Minor Improvements\n\n- Fix custom gogo codec to allow OTLP data ([@yurishkuro](https://github.com/yurishkuro) in [#3719](https://github.com/jaegertracing/jaeger/pull/3719))\n\n1.35.0 (2022-06-01)\n-------------------\n### Backend Changes\n\n#### New Features\n\n- Introduce OTLP receiver configuration flags ([@yurishkuro](https://github.com/yurishkuro) in [#3710](https://github.com/jaegertracing/jaeger/pull/3710))\n- Define Health Server for GRPC servers ([@mmorel-35](https://github.com/mmorel-35) in [#3712](https://github.com/jaegertracing/jaeger/pull/3712))\n- Add OTLP receiver to collector ([@yurishkuro](https://github.com/yurishkuro) in [#3701](https://github.com/jaegertracing/jaeger/pull/3701))\n- Add flag to enable/disable dependencies on rollover ([@rubenvp8510](https://github.com/rubenvp8510) in [#3705](https://github.com/jaegertracing/jaeger/pull/3705))\n- Add TLS configuration for Admin Server ([@mmorel-35](https://github.com/mmorel-35) in [#3679](https://github.com/jaegertracing/jaeger/pull/3679))\n- Add TLS configuration for Zipkin ([@mmorel-35](https://github.com/mmorel-35) in [#3676](https://github.com/jaegertracing/jaeger/pull/3676))\n\n#### Bug fixes, Minor Improvements\n\n- Fix for WithTenant() lost orig context ([@esnible](https://github.com/esnible) in [#3685](https://github.com/jaegertracing/jaeger/pull/3685))\n- Add entries to env command for new storage types ([@yurishkuro](https://github.com/yurishkuro) in [#3678](https://github.com/jaegertracing/jaeger/pull/3678))\n- Fix Prometheus factory signature ([@yurishkuro](https://github.com/yurishkuro) in [#3681](https://github.com/jaegertracing/jaeger/pull/3681))\n\n### UI Changes\n\n* UI pinned to version [1.24.0 - see the changelog](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1240-2022-06-01).\n\n\n1.34.0 (2022-05-11)\n-------------------\n### Backend Changes\n\n#### New Features\n\n* Add kubernetes example for hotrod app ([@highb](https://github.com/highb) in [#3645](https://github.com/jaegertracing/jaeger/pull/3645))\n* Support writing via gRPC stream in storage plugin ([@vuuihc](https://github.com/vuuihc) in [#3640](https://github.com/jaegertracing/jaeger/pull/3640))\n* Instrument MetricsReader with metrics ([@albertteoh](https://github.com/albertteoh) in [#3667](https://github.com/jaegertracing/jaeger/pull/3667))\n\n#### Bug fixes, Minor Improvements\n\n* Sanitize spans with null process or empty service name ([@yurishkuro](https://github.com/yurishkuro) in [#3631](https://github.com/jaegertracing/jaeger/pull/3631))\n* Flow tenant through processors as a string ([@esnible](https://github.com/esnible) in [#3661](https://github.com/jaegertracing/jaeger/pull/3661))\n* Fix es.log-level behaviour ([@albertteoh](https://github.com/albertteoh) in [#3664](https://github.com/jaegertracing/jaeger/pull/3664))\n* Mention remote gRPC storage API ([@yurishkuro](https://github.com/yurishkuro) in [cb6853d](https://github.com/jaegertracing/jaeger/commit/cb6853d4eea1ab5430f5e8db6b603cd7de5a16c3))\n* Perform log.fatal if TLS flags are used when tls.enabled=false #2893 ([@clock21am](https://github.com/clock21am) in [#3030](https://github.com/jaegertracing/jaeger/pull/3030))\n* Upgrade to Go 1.18 ([@yurishkuro](https://github.com/yurishkuro) in [#3624](https://github.com/jaegertracing/jaeger/pull/3624))\n\n### UI Changes\n\n* UI pinned to version 1.23.0. The changelog is available here [v1.23.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1230-2022-05-10).\n\n1.33.0 (2022-04-11)\n-------------------\n#### New Features\n\n* Add SAMPLING_STORAGE_TYPE environment variable ([@joe-elliott](https://github.com/joe-elliott) in [#3573](https://github.com/jaegertracing/jaeger/pull/3573))\n* Support min/max TLS version in TLS config ([@Ashmita152](https://github.com/Ashmita152) in [#3567](https://github.com/jaegertracing/jaeger/pull/3567))\n* Add support for ciphersuites in tls config ([@Ashmita152](https://github.com/Ashmita152) in [#3564](https://github.com/jaegertracing/jaeger/pull/3564))\n\n#### Bug fixes, Minor Improvements\n\n* Fix: exit on grpc plugin crash ([@johanneswuerbach](https://github.com/johanneswuerbach) in [#3604](https://github.com/jaegertracing/jaeger/pull/3604))\n* Bump go.opentelemetry.io/collector/model from 0.47.0 to 0.48.0 ([@dependabot[bot]](https://github.com/apps/dependabot) in [#3608](https://github.com/jaegertracing/jaeger/pull/3608))\n* Fix format string for go1.18 ([@bobrik](https://github.com/bobrik) in [#3596](https://github.com/jaegertracing/jaeger/pull/3596))\n* Fix favicon returning 500 inside container ([@Ashmita152](https://github.com/Ashmita152) in [#3569](https://github.com/jaegertracing/jaeger/pull/3569))\n* Elasticsearch: Do not create index templates if ILM is enabled. ([@rbizos](https://github.com/rbizos) in [#3610](https://github.com/jaegertracing/jaeger/pull/3610))\n\n\n### UI Changes\n\n* UI pinned to version 1.22.0. The changelog is available here [v1.22.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1220-2022-04-11).\n\n1.32.0 (2022-03-06)\n-------------------\n\n### Backend Changes\n\n#### New Features\n\n* Enable gRPC reflection service on collector/query ([@yurishkuro](https://github.com/yurishkuro) in [#3526](https://github.com/jaegertracing/jaeger/pull/3526))\n\n#### Bug fixes, Minor Improvements\n\n* Fix query for latency metrics ([@Ashmita152](https://github.com/Ashmita152) in [#3559](https://github.com/jaegertracing/jaeger/pull/3559))\n* Fix integration tests containing spans in the future ([@johanneswuerbach](https://github.com/johanneswuerbach) in [#3538](https://github.com/jaegertracing/jaeger/pull/3538))\n* Add system diagram using mermaid markdown ([@yurishkuro](https://github.com/yurishkuro) in [#3529](https://github.com/jaegertracing/jaeger/pull/3529))\n* Fix indexDateLayout for elasticsearch dependencies #3523 ([@ilyamor](https://github.com/ilyamor) in [#3524](https://github.com/jaegertracing/jaeger/pull/3524))\n* Fix builds due to upstream OTEL proto path change ([@albertteoh](https://github.com/albertteoh) in [#3525](https://github.com/jaegertracing/jaeger/pull/3525))\n\n### UI Changes\n\n* UI pinned to version 1.21.0. The changelog is available here [v1.21.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1210-2022-03-06).\n\n1.31.0 (2022-02-04)\n-------------------\n### Backend Changes\n#### Bug fixes, Minor Improvements\n\n* Bump Go compiler in CI to 1.17.6 ([@yurishkuro](https://github.com/yurishkuro) in [#3516](https://github.com/jaegertracing/jaeger/pull/3516))\n* Add support for ES index aliases / rollover to the dependency store (Resolves #2143) ([@frittentheke](https://github.com/frittentheke) in [#2144](https://github.com/jaegertracing/jaeger/pull/2144))\n* Use existing functions from xdg-go/scram pkg ([@yurishkuro](https://github.com/yurishkuro) in [#3481](https://github.com/jaegertracing/jaeger/pull/3481))\n\n### UI Changes\n\n* UI pinned to version 1.20.1. The changelog is available here [v1.20.1](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1201-2022-02-04).\n\n\n1.30.0 (2022-01-11)\n-------------------\n### Backend Changes\n\n#### New Features\n\n* Add remote gRPC option for storage plugin ([@cevian](https://github.com/cevian) in [#3383](https://github.com/jaegertracing/jaeger/pull/3383))\n* Build binaries for darwin/arm64 ([@jhchabran](https://github.com/jhchabran) in [#3431](https://github.com/jaegertracing/jaeger/pull/3431))\n* Add MaxConnectionAge[Grace] to collector's gRPC server ([@jpkrohling](https://github.com/jpkrohling) in [#3422](https://github.com/jaegertracing/jaeger/pull/3422))\n\n#### Bug fixes, Minor Improvements\n\n* Fix prefixed index rollover ([@albertteoh](https://github.com/albertteoh) in [#3457](https://github.com/jaegertracing/jaeger/pull/3457))\n* Log problems communicating with Elasticsearch ([@esnible](https://github.com/esnible) in [#3451](https://github.com/jaegertracing/jaeger/pull/3451))\n\n### UI Changes\n\n* UI pinned to version 1.20.0. The changelog is available here [v1.20.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1200-jan-11-2022)\n\n1.29.0 (2021-12-01)\n-------------------\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove deprecated `--badger.truncate` CLI flag ([@yurishkuro](https://github.com/yurishkuro) in [#3410](https://github.com/jaegertracing/jaeger/pull/3410))\n\n#### New Features\n\n* Expose rackID option in ingester ([@shyimo](https://github.com/shyimo) in [#3395](https://github.com/jaegertracing/jaeger/pull/3395))\n\n#### Bug fixes, Minor Improvements\n\n* Fix debug image builds by installing `build-base` to enable GCC ([@yurishkuro](https://github.com/yurishkuro) in [#3400](https://github.com/jaegertracing/jaeger/pull/3400))\n* Limit URL size in Elasticsearch index delete request ([@jkandasa](https://github.com/jkandasa) in [#3375](https://github.com/jaegertracing/jaeger/pull/3375))\n\n### UI Changes\n\n* UI pinned to version 1.19.0. The changelog is available here [v1.19.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1190-dec-1-2021)\n\n1.28.0 (2021-11-06)\n-------------------\n### Backend Changes\n* Add auth token propagation for metrics reader ([@albertteoh](https://github.com/albertteoh) in [#3341](https://github.com/jaegertracing/jaeger/pull/3341))\n\n#### New Features\n* Add in-memory storage support for adaptive sampling ([@lonewolf3739](https://github.com/lonewolf3739) in [#3335](https://github.com/jaegertracing/jaeger/pull/3335))\n\n#### Bug fixes, Minor Improvements\n\n* Do not throw error on empty indices in Elasticsach rollover lookback ([@jkandasa](https://github.com/jkandasa) in [#3369](https://github.com/jaegertracing/jaeger/pull/3369))\n* Treat input throughput data as immutable ([@rbroggi](https://github.com/rbroggi) in [#3360](https://github.com/jaegertracing/jaeger/pull/3360))\n* Remove dependencies on unused tools, install tools explicitly instead of via go.mod ([@rbroggi](https://github.com/rbroggi) in [#3355](https://github.com/jaegertracing/jaeger/pull/3355))\n* Update mockery to version 2 and adapt to `install-tools` approach ([@rbroggi](https://github.com/rbroggi) in [#3358](https://github.com/jaegertracing/jaeger/pull/3358))\n* Control lightweight storage integration tests via build tags ([@rbroggi](https://github.com/rbroggi) in [#3346](https://github.com/jaegertracing/jaeger/pull/3346))\n* Remove package `integration` from coverage reports ([@rbroggi](https://github.com/rbroggi) in [#3357](https://github.com/jaegertracing/jaeger/pull/3357))\n* Remove outdated reference to cover.sh ([@rbroggi](https://github.com/rbroggi) in [#3348](https://github.com/jaegertracing/jaeger/pull/3348))\n* Update monitoring mixin ([@jpkrohling](https://github.com/jpkrohling) in [#3331](https://github.com/jaegertracing/jaeger/pull/3331))\n* Update Jaeger chart link ([@isbang](https://github.com/isbang) in [#3328](https://github.com/jaegertracing/jaeger/pull/3328))\n* Fix args order in strings.Contains in es-rollover ([@pavolloffay](https://github.com/pavolloffay) in [#3324](https://github.com/jaegertracing/jaeger/pull/3324))\n* Use `(TB).TempDir` instead of non-portable `/mnt/*` in Badger ([@pavolloffay](https://github.com/pavolloffay) in [#3325](https://github.com/jaegertracing/jaeger/pull/3325))\n* Fix `peer.service` retrieval from Zipkin's `MESSAGE_ADDR` annotation ([@Git-Jiro](https://github.com/Git-Jiro) in [#3312](https://github.com/jaegertracing/jaeger/pull/3312))\n\n### UI Changes\n\n* UI pinned to version 1.18.0. The changelog is available here [v1.18.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1180-nov-6-2021)\n\n\n1.27.0 (2021-10-06)\n-------------------\n### Backend Changes\n* Migrate elasticsearch rollover to go ([#3242](https://github.com/jaegertracing/jaeger/pull/3242), [@rubenvp8510](https://github.com/rubenvp8510))\n* Add 'opensearch' as a supported value for SPAN_STORAGE_TYPE ([#3255](https://github.com/jaegertracing/jaeger/pull/3255), [@yurishkuro](https://github.com/yurishkuro))\n\n#### New Features\n* Add support for adaptive sampling with a Cassandra backend. ([#2966](https://github.com/jaegertracing/jaeger/pull/2966), [@joe-elliott](https://github.com/joe-elliott), [@Ashmita152](https://github.com/Ashmita152))\n\n#### Bug fixes, Minor Improvements\n* Support graceful shutdown in grpc plugin ([#3249](https://github.com/jaegertracing/jaeger/pull/3249), [@slon](https://github.com/slon))\n* Enable gzip compression for collector grpc endpoint. ([#3236](https://github.com/jaegertracing/jaeger/pull/3236), [@slon](https://github.com/slon))\n* Use UTC in es-index-cleaner ([#3261](https://github.com/jaegertracing/jaeger/pull/3261), [@pavolloffay](https://github.com/pavolloffay))\n* Upgrade to alpine-3.14 ([#3304](https://github.com/jaegertracing/jaeger/pull/3304), [@nontw](https://github.com/nontw))\n* refactor: move from io/ioutil to io and os package ([#3294](https://github.com/jaegertracing/jaeger/pull/3294), [@Juneezee](https://github.com/Juneezee))\n* Changed sampling type env var and updated collector help text ([#3302](https://github.com/jaegertracing/jaeger/pull/3302), [@joe-elliott](https://github.com/joe-elliott))\n* Close #3270: Prevent rollover lookback from passing the Unix epoch ([#3299](https://github.com/jaegertracing/jaeger/pull/3299), [@ctreatma](https://github.com/ctreatma))\n* Fixing otel configuration in docker compose ([#3286](https://github.com/jaegertracing/jaeger/pull/3286), [@Ashmita152](https://github.com/Ashmita152))\n* Added ability to pass config file to grpc plugin in integration tests ([#3253](https://github.com/jaegertracing/jaeger/pull/3253), [@EinKrebs](https://github.com/EinKrebs))\n\n### UI Changes\n\n* UI pinned to version 1.17.0. The changelog is available here [v1.17.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1170-oct-6-2021)\n\n\n1.26.0 (2021-09-06)\n-------------------\n### Backend Changes\n#### New Features\n* Add cassandra v4 support ([@Ashmita152](https://github.com/Ashmita152) in [#3225](https://github.com/jaegertracing/jaeger/pull/3225)).\n  * **Note**: Users running on existing schema version [v3](https://github.com/jaegertracing/jaeger/blob/1d563f964da4b472a6f7e4cfdb85fe1167c30129/plugin/storage/cassandra/schema/v003.cql.tmpl) without issues, need not run an upgrade to [v4](https://github.com/jaegertracing/jaeger/blob/1d563f964da4b472a6f7e4cfdb85fe1167c30129/plugin/storage/cassandra/schema/v004.cql.tmpl). The new schema simply updates the table metadata, primarily removing `dclocal_read_repair_chance` deprecated table option, [which automatically gets ignored/removed when upgrading to Cassandra 4.0](https://issues.apache.org/jira/browse/CASSANDRA-13910).\n* Add es-index-cleaner golang implementation ([@pavolloffay](https://github.com/pavolloffay) in [#3204](https://github.com/jaegertracing/jaeger/pull/3204))\n* Add CLI Option for gRPC Max Receive Message Size ([@js8080](https://github.com/js8080) in [#3214](https://github.com/jaegertracing/jaeger/pull/3214) and [#3192](https://github.com/jaegertracing/jaeger/pull/3192))\n* Automatically detect OpenSearch version ([@pavolloffay](https://github.com/pavolloffay) in [#3198](https://github.com/jaegertracing/jaeger/pull/3198))\n* Add SendGetBodyAs on elasticsearch ([@NatMarchand](https://github.com/NatMarchand) in [#3193](https://github.com/jaegertracing/jaeger/pull/3193))\n* Set lookback in ES rollover to distant past ([@pavolloffay](https://github.com/pavolloffay) in [#3169](https://github.com/jaegertracing/jaeger/pull/3169))\n\n#### Bug fixes, Minor Improvements\n* Check for invalid --agent.tags ([@esnible](https://github.com/esnible) in [#3246](https://github.com/jaegertracing/jaeger/pull/3246))\n* Replace old linters with golangci-lint ([@albertteoh](https://github.com/albertteoh) in [#3237](https://github.com/jaegertracing/jaeger/pull/3237))\n* Fix panic on empty findTraces query ([@akuzni2](https://github.com/akuzni2) in [#3232](https://github.com/jaegertracing/jaeger/pull/3232))\n* Upgrade to Go v1.17 ([@Ashmita152](https://github.com/Ashmita152) in [#3220](https://github.com/jaegertracing/jaeger/pull/3220))\n* Add docker buildx make target ([@pavolloffay](https://github.com/pavolloffay) in [#3213](https://github.com/jaegertracing/jaeger/pull/3213))\n* Fix the name for elasticsearch integration tests ([@Ashmita152](https://github.com/Ashmita152) in [#3208](https://github.com/jaegertracing/jaeger/pull/3208))\n* Upgrade ES images in integration tests ([@pavolloffay](https://github.com/pavolloffay) in [#3185](https://github.com/jaegertracing/jaeger/pull/3185))\n\n### UI Changes\n* UI pinned to version 1.16.0 - https://github.com/jaegertracing/jaeger-ui/releases/tag/v1.16.0\n\n1.25.0 (2021-08-04)\n-------------------\n#### New Features\n* Add query service with OTLP ([#3086](https://github.com/jaegertracing/jaeger/pull/3086), [@pavolloffay](https://github.com/pavolloffay))\n* Add ppc64le support on multiarch docker images ([#3160](https://github.com/jaegertracing/jaeger/pull/3160), [@krishvoor](https://github.com/krishvoor))\n\n#### Bug fixes, Minor Improvements\n* Fix base path in grpc gateway for api_v3 ([#3139](https://github.com/jaegertracing/jaeger/pull/3139), [@pavolloffay](https://github.com/pavolloffay))\n* Add /api prefix for /v3 API ([#3178](https://github.com/jaegertracing/jaeger/pull/3178), [@pavolloffay](https://github.com/pavolloffay))\n* Define `http.Server.ErrorLog` to forward logs to Zap ([#3157](https://github.com/jaegertracing/jaeger/pull/3157), [@yurishkuro](https://github.com/yurishkuro))\n* Add ATM dev environment docker-compose and API doc ([#3171](https://github.com/jaegertracing/jaeger/pull/3171), [@albertteoh](https://github.com/albertteoh))\n* Log the source of sampling strategies ([#3166](https://github.com/jaegertracing/jaeger/pull/3166), [@yurishkuro](https://github.com/yurishkuro))\n* Pin elasticsearch-py to older version without elastic.co product check ([#3180](https://github.com/jaegertracing/jaeger/pull/3180), [@pavolloffay](https://github.com/pavolloffay))\n\n1.24.0 (2021-07-07)\n-------------------\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Upgrade Badger from v1.6.2 to v3.2103.0 ([#3096](https://github.com/jaegertracing/jaeger/pull/3096), [@Ashmita152](https://github.com/Ashmita152)):\n  * Deprecated `--badger.truncate` flag.\n  * All badger related expvar prefix has changed from `badger` to `badger_v3`.\n\n#### New Features\n\n* Add docker images for linux/arm64 ([#3124](https://github.com/jaegertracing/jaeger/pull/3124), [@GaruGaru](https://github.com/GaruGaru))\n* Add s390x support on multiarch docker images ([#2948](https://github.com/jaegertracing/jaeger/pull/2948), [@kun-lu20](https://github.com/kun-lu20))\n* Add TLS support for Prometheus reader ([#3096](https://github.com/jaegertracing/jaeger/pull/3096), [@albertteoh](https://github.com/albertteoh))\n\n##### [Monitor tab for service health metrics](https://github.com/jaegertracing/jaeger/issues/2954)\n\n* Add HTTP handler for metrics querying [#3095](https://github.com/jaegertracing/jaeger/pull/3095), [@albertteoh](https://github.com/albertteoh))\n* Add MetricsQueryService grcp handler [#3091](https://github.com/jaegertracing/jaeger/pull/3091), [@albertteoh](https://github.com/albertteoh))\n* Hook up MetricsQueryService to main funcs ([#3079](https://github.com/jaegertracing/jaeger/pull/3079), [@albertteoh](https://github.com/albertteoh))\n* Add metrics query capability to query service ([#3061](https://github.com/jaegertracing/jaeger/pull/3061), [@albertteoh](https://github.com/albertteoh))\n\n#### Bug fixes, Minor Improvements\n\n* Add build info metrics to Jaeger components ([#3087](https://github.com/jaegertracing/jaeger/pull/3087), [@Ashmita152](https://github.com/Ashmita152))\n* Upgrade gRPC to 1.38.x ([#3096](https://github.com/jaegertracing/jaeger/pull/3096), [@pavolloffay](https://github.com/pavolloffay))\n\n1.23.0 (2021-06-04)\n-------------------\n### Backend Changes\n\n#### New Features\n\n#### ⛔ Breaking Changes\n\n* Remove unused `--es-archive.max-span-age` flag ([#2865](https://github.com/jaegertracing/jaeger/pull/2865), [@albertteoh](https://github.com/albertteoh)):\n\n#### New Features\n\n* Inject trace context to grpc metadata ([#2870](https://github.com/jaegertracing/jaeger/pull/2870), [@lujiajing1126](https://github.com/lujiajing1126))\n* Passing default sampling strategies file as environment variable ([#3027](https://github.com/jaegertracing/jaeger/pull/3027), [@Ashmita152](https://github.com/Ashmita152))\n* [es] Add index rollover mode that can choose day and hour ([#2965](https://github.com/jaegertracing/jaeger/pull/2965), [@WalkerWang731](https://github.com/WalkerWang731))\n* Add a TIMEOUT environment variable for es rollover ([#2938](https://github.com/jaegertracing/jaeger/pull/2938), [@ediezh](https://github.com/ediezh))\n* Allow the ILM policy name to be configurable ([#2971](https://github.com/jaegertracing/jaeger/pull/2971), [@jrRibeiro](https://github.com/jrRibeiro))\n* [es] Add remote read clusters option for cross-cluster querying ([#2874](https://github.com/jaegertracing/jaeger/pull/2874), [@dgrizzanti](https://github.com/dgrizzanti))\n* Enable logging in ES client ([#2862](https://github.com/jaegertracing/jaeger/pull/2862), [@albertteoh](https://github.com/albertteoh))\n\n#### Bug fixes, Minor Improvements\n\n* Fix jaeger-agent reproducible memory leak ([#3050](https://github.com/jaegertracing/jaeger/pull/3050), [@jpkrohling](https://github.com/jpkrohling))\n* Changed Range Query to use startTimeMillis date field instead of startTime field ([#2980](https://github.com/jaegertracing/jaeger/pull/2980), [@Sreevani871](https://github.com/Sreevani871))\n* Verify FindTraces() received a query ([#2979](https://github.com/jaegertracing/jaeger/pull/2979), [@esnible](https://github.com/esnible))\n* Set Content-Type in healthcheck's http response ([#2926](https://github.com/jaegertracing/jaeger/pull/2926), [@logeable](https://github.com/logeable))\n* Add jaeger-query HTTP handler diagnostic logging ([#2906](https://github.com/jaegertracing/jaeger/pull/2906), [@albertteoh](https://github.com/albertteoh))\n* Fix es-archive namespace default values ([#2865](https://github.com/jaegertracing/jaeger/pull/2865), [@albertteoh](https://github.com/albertteoh))\n\n1.22.0 (2021-02-23)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove deprecated TLS flags ([#2790](https://github.com/jaegertracing/jaeger/issues/2790), [@albertteoh](https://github.com/albertteoh)):\n    * `--cassandra.tls` is replaced by `--cassandra.tls.enabled`\n    * `--cassandra-archive.tls` is replaced by `--cassandra-archive.tls.enabled`\n    * `--collector.grpc.tls` is replaced by `--collector.grpc.tls.enabled`\n    * `--collector.grpc.tls.client.ca` is replaced by `--collector.grpc.tls.client-ca`\n    * `--es.tls` is replaced by `--es.tls.enabled`\n    * `--es-archive.tls` is replaced by `--es-archive.tls.enabled`\n    * `--kafka.consumer.tls` is replaced by `--kafka.consumer.tls.enabled`\n    * `--kafka.producer.tls` is replaced by `--kafka.producer.tls.enabled`\n    * `--reporter.grpc.tls` is replaced by `--reporter.grpc.tls.enabled`\n\n* Remove deprecated flags of Query Server  `--query.port` and `--query.host-port`, please use dedicated HTTP `--query.http-server.host-port` (defaults to `:16686`) and gRPC `--query.grpc-server.host-port` (defaults to `:16685`)  host-ports flags instead ([#2772](https://github.com/jaegertracing/jaeger/pull/2772), [@rjs211](https://github.com/rjs211))\n    * By default, if no flags are set, the query server starts on the dedicated ports.  To use common port for gRPC and  HTTP endpoints, the host-port flags have to be explicitly set\n\n* Remove deprecated CLI flags ([#2751](https://github.com/jaegertracing/jaeger/issues/2751), [@LostLaser](https://github.com/LostLaser)):\n    * `--collector.http-port` is replaced by `--collector.http-server.host-port`\n    * `--collector.grpc-port` is replaced by `--collector.grpc-server.host-port`\n    * `--collector.zipkin.http-port` is replaced by `--collector.zipkin.host-port`\n\n* Remove deprecated flags `--health-check-http-port` & `--admin-http-port`, please use `--admin.http.host-port` ([#2752](https://github.com/jaegertracing/jaeger/pull/2752), [@pradeepnnv](https://github.com/pradeepnnv))\n\n* Remove deprecated flag `--es.max-num-spans`, please use `--es.max-doc-count` ([#2482](https://github.com/jaegertracing/jaeger/pull/2482), [@BernardTolosajr](https://github.com/BernardTolosajr))\n\n* Remove deprecated flag `--jaeger.tags`, please use `--agent.tags` ([#2753](https://github.com/jaegertracing/jaeger/pull/2753), [@yurishkuro](https://github.com/yurishkuro))\n\n* Remove deprecated Cassandra flags ([#2789](https://github.com/jaegertracing/jaeger/pull/2789), [@albertteoh](https://github.com/albertteoh)):\n    * `--cassandra.enable-dependencies-v2` - Jaeger will automatically detect the version of the dependencies table\n    * `--cassandra.tls.verify-host` - please use `--cassandra.tls.skip-host-verify` instead\n\n* Remove incorrectly scoped downsample flags from the query service ([#2782](https://github.com/jaegertracing/jaeger/pull/2782), [@joe-elliott](https://github.com/joe-elliott))\n    * `--downsampling.hashsalt` removed from jaeger-query\n    * `--downsampling.ratio` removed from jaeger-query\n\n#### New Features\n\n* Add TLS Support for gRPC and HTTP endpoints of the Query and Collector servers ([#2337](https://github.com/jaegertracing/jaeger/pull/2337), [#2772](https://github.com/jaegertracing/jaeger/pull/2772), [#2798](https://github.com/jaegertracing/jaeger/pull/2798), [@rjs211](https://github.com/rjs211))\n\n    *  If TLS in enabled on either or both of gRPC or HTTP endpoints, the gRPC host-port and the HTTP host-port have to be different\n    *  If TLS is disabled on both endpoints, common HTTP and gRPC host-port can be explicitly set using the following host-port flags respectively:\n        * Query: `--query.http-server.host-port` and  `--query.grpc-server.host-port`\n        * Collector: `--collector.http-server.host-port` and `--collector.grpc-server.host-port`\n* Add support for Kafka SASL/PLAIN authentication via SCRAM-SHA-256 or SCRAM-SHA-512 mechanism ([#2724](https://github.com/jaegertracing/jaeger/pull/2724), [@WalkerWang731](https://github.com/WalkerWang731))\n* [agent] Add metrics to show connections status between agent and collectors ([#2657](https://github.com/jaegertracing/jaeger/pull/2657), [@WalkerWang731](https://github.com/WalkerWang731))\n* Add plaintext as a supported kafka auth option ([#2721](https://github.com/jaegertracing/jaeger/pull/2721), [@pdepaepe](https://github.com/pdepaepe))\n* Add ability to use JS file for UI configuration (#123 from jaeger-ui) ([#2707](https://github.com/jaegertracing/jaeger/pull/2707), [@th3M1ke](https://github.com/th3M1ke))\n* Support Elasticsearch ILM for managing jaeger indices ([#2796](https://github.com/jaegertracing/jaeger/pull/2796), [@bhiravabhatla](https://github.com/bhiravabhatla))\n* Push official images to quay.io, in addition to Docker Hub ([#2783](https://github.com/jaegertracing/jaeger/pull/2783), [@Ashmita152](https://github.com/Ashmita152))\n* Add status command ([#2684](https://github.com/jaegertracing/jaeger/pull/2684), [@sniperking1234](https://github.com/sniperking1234))\n    * Usage:\n      ```bash\n      $ ./cmd/collector/collector-darwin-amd64 status\n      {\"status\":\"Server available\",\"upSince\":\"2021-02-19T17:57:12.671902+11:00\",\"uptime\":\"25.241233383s\"}\n      ```\n* Support configurable date separator for Elasticsearch index names ([#2637](https://github.com/jaegertracing/jaeger/pull/2637), [@sniperking1234](https://github.com/sniperking1234))\n\n#### Bug fixes, Minor Improvements\n\n* Use workaround for windows x509.SystemCertPool() ([#2756](https://github.com/jaegertracing/jaeger/pull/2756), [@Ashmita152](https://github.com/Ashmita152))\n* Guard against mal-formed payloads received by the agent, potentially causing high memory utilization ([#2780](https://github.com/jaegertracing/jaeger/pull/2780), [@jpkrohling](https://github.com/jpkrohling))\n* Expose cache TTL for ES span writer index+service ([#2737](https://github.com/jaegertracing/jaeger/pull/2737), [@necrolyte2](https://github.com/necrolyte2))\n* Copy spans from memory store ([#2720](https://github.com/jaegertracing/jaeger/pull/2720), [@bobrik](https://github.com/bobrik))\n* [pkg/queue] Add `StartConsumersWithFactory` function ([#2714](https://github.com/jaegertracing/jaeger/pull/2714), [@mx-psi](https://github.com/mx-psi))\n* Fix potential cross-site scripting issue ([#2697](https://github.com/jaegertracing/jaeger/pull/2697), [@yurishkuro](https://github.com/yurishkuro))\n* Updated gRPC Storage Plugin README with example ([#2687](https://github.com/jaegertracing/jaeger/pull/2687), [@js8080](https://github.com/js8080))\n* Deduplicate collector tags ([#2658](https://github.com/jaegertracing/jaeger/pull/2658), [@Betula-L](https://github.com/Betula-L))\n* Add latency metrics on collector HTTP endpoints ([#2664](https://github.com/jaegertracing/jaeger/pull/2664), [@dimitarvdimitrov](https://github.com/dimitarvdimitrov))\n* Fix collector panic due to sarama sdk ([#2654](https://github.com/jaegertracing/jaeger/pull/2654), [@Betula-L](https://github.com/Betula-L))\n* Handle collector Start error ([#2647](https://github.com/jaegertracing/jaeger/pull/2647), [@albertteoh](https://github.com/albertteoh))\n* [anonymizer] Save trace in UI format ([#2629](https://github.com/jaegertracing/jaeger/pull/2629), [@yurishkuro](https://github.com/yurishkuro))\n\n### UI Changes\n\n* UI pinned to version 1.13.0. The changelog is available here [v1.13.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1130-february-20-2021)\n\n\n1.21.0 (2020-11-13)\n-------------------\n\n### Backend Changes\n\n#### New Features\n\n* Add trace anonymizer utility ([#2621](https://github.com/jaegertracing/jaeger/pull/2621), [#2585](https://github.com/jaegertracing/jaeger/pull/2585), [@Ashmita152](https://github.com/Ashmita152))\n* Add URL option for sampling strategies file ([#2519](https://github.com/jaegertracing/jaeger/pull/2519), [@goku321](https://github.com/goku321))\n* Expose tunning options via expvar ([#2496](https://github.com/jaegertracing/jaeger/pull/2496), [@dstdfx](https://github.com/dstdfx))\n* Support more encodings for Kafka in OTel Ingester ([#2580](https://github.com/jaegertracing/jaeger/pull/2580), [@XSAM](https://github.com/XSAM))\n* Create debug docker images for jaeger backends ([#2545](https://github.com/jaegertracing/jaeger/pull/2545), [@Ashmita152](https://github.com/Ashmita152))\n* Display backend & UI versions in Jaeger UI\n  * Inject version info into index.html ([#2547](https://github.com/jaegertracing/jaeger/pull/2547), [@yurishkuro](https://github.com/yurishkuro))\n  * Added jaeger ui version to about menu ([#606](https://github.com/jaegertracing/jaeger-ui/pull/606), [@alanisaac](https://github.com/alanisaac))\n\n#### Bug fixes, Minor Improvements\n\n* Update x/text to v0.3.4 ([#2625](https://github.com/jaegertracing/jaeger/pull/2625), [@objectiser](https://github.com/objectiser))\n* Update CodeQL to latest best practices ([#2615](https://github.com/jaegertracing/jaeger/pull/2615), [@jhutchings1](https://github.com/jhutchings1))\n* Bump opentelemetry-collector to v0.14.0 ([#2617](https://github.com/jaegertracing/jaeger/pull/2617), [@Vemmy124](https://github.com/Vemmy124))\n* Bump Badger to v1.6.2 ([#2613](https://github.com/jaegertracing/jaeger/pull/2613), [@Ackar](https://github.com/Ackar))\n* Fix sarama consumer deadlock ([#2587](https://github.com/jaegertracing/jaeger/pull/2587), [@albertteoh](https://github.com/albertteoh))\n* Avoid deadlock if Stop is called before Serve ([#2608](https://github.com/jaegertracing/jaeger/pull/2608), [@chlunde](https://github.com/chlunde))\n* Return buffers to pool on network errors or queue overflow ([#2609](https://github.com/jaegertracing/jaeger/pull/2609), [@chlunde](https://github.com/chlunde))\n* Clarify deadlock panic message ([#2605](https://github.com/jaegertracing/jaeger/pull/2605), [@yurishkuro](https://github.com/yurishkuro))\n* fix: don't create tags w/ empty name for internal zipkin spans ([#2596](https://github.com/jaegertracing/jaeger/pull/2596), [@mzahor](https://github.com/mzahor))\n* TBufferedServer: Avoid channel close/send race on Stop ([#2583](https://github.com/jaegertracing/jaeger/pull/2583), [@chlunde](https://github.com/chlunde))\n* Bumped OpenTelemetry Collector to v0.12.0 ([#2562](https://github.com/jaegertracing/jaeger/pull/2562), [@jpkrohling](https://github.com/jpkrohling))\n* Disable Zipkin server if port/address is not configured ([#2554](https://github.com/jaegertracing/jaeger/pull/2554), [@yurishkuro](https://github.com/yurishkuro))\n* [hotrod] Add links to traces ([#2536](https://github.com/jaegertracing/jaeger/pull/2536), [@yurishkuro](https://github.com/yurishkuro))\n* OTel Cassandra/Elasticsearch Exporter queue defaults ([#2533](https://github.com/jaegertracing/jaeger/pull/2533), [@joe-elliott](https://github.com/joe-elliott))\n* [otel] Update jaeger-lib to v2.4.0 ([#2538](https://github.com/jaegertracing/jaeger/pull/2538), [@dstdfx](https://github.com/dstdfx))\n* Remove unnecessary ServiceName index seek if tags query is available ([#2535](https://github.com/jaegertracing/jaeger/pull/2535), [@burmanm](https://github.com/burmanm))\n* Update static UI assets path in contrib doc ([#2548](https://github.com/jaegertracing/jaeger/pull/2548), [@albertteoh](https://github.com/albertteoh))\n\n### UI Changes\n\n* UI pinned to version 1.12.0. The changelog is available here [v1.12.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1120-november-14-2020)\n\n\n1.20.0 (2020-09-29)\n-------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n* Configurable ES doc count ([#2453](https://github.com/jaegertracing/jaeger/pull/2453), [@albertteoh](https://github.com/albertteoh))\n\n    The `--es.max-num-spans` flag has been deprecated in favour of `--es.max-doc-count`.\n    `--es.max-num-spans` is marked for removal in v1.21.0 as indicated in the flag description.\n\n    If both `--es.max-num-spans` and `--es.max-doc-count` are set, the lesser of the two will be used.\n\n    The use of `--es.max-doc-count` (which defaults to 10,000) will limit the results from all Elasticsearch\n    queries by the configured value, limiting counts for Jaeger UI:\n\n    * Services\n    * Operations\n    * Dependencies (edges in a dependency graph)\n    * Span fetch size for a trace\n\n* The default value for the flag `query.max-clock-skew-adjustment` has changed to `0s`, meaning that the clock skew adjustment is now disabled by default. See [#1459](https://github.com/jaegertracing/jaeger/issues/1459).\n\n#### New Features\n\n* Grpc plugin archive storage support ([#2317](https://github.com/jaegertracing/jaeger/pull/2317), [@m8rge](https://github.com/m8rge))\n* Separate Ports for GRPC and HTTP requests in Query Server ([#2387](https://github.com/jaegertracing/jaeger/pull/2387), [@rjs211](https://github.com/rjs211))\n* Configurable ES doc count ([#2453](https://github.com/jaegertracing/jaeger/pull/2453), [@albertteoh](https://github.com/albertteoh))\n* Add storage metrics to OTEL, metrics by span service name ([#2431](https://github.com/jaegertracing/jaeger/pull/2431), [@pavolloffay](https://github.com/pavolloffay))\n\n#### Bug fixes, Minor Improvements\n\n* Increase coverage on otel/app/defaultconfig and otel/app/defaultcomponents ([#2515](https://github.com/jaegertracing/jaeger/pull/2515), [@joe-elliott](https://github.com/joe-elliott))\n* Use OTEL Kafka Exporter/Receiver Instead of Jaeger Core ([#2494](https://github.com/jaegertracing/jaeger/pull/2494), [@joe-elliott](https://github.com/joe-elliott))\n* Fix OTEL kafka receiver/ingester panic ([#2512](https://github.com/jaegertracing/jaeger/pull/2512), [@pavolloffay](https://github.com/pavolloffay))\n* Disable clock-skew-adjustment by default. ([#2513](https://github.com/jaegertracing/jaeger/pull/2513), [@jpkrohling](https://github.com/jpkrohling))\n* Fix ES OTEL status code ([#2501](https://github.com/jaegertracing/jaeger/pull/2501), [@pavolloffay](https://github.com/pavolloffay))\n* OTel: Factored out Config Factory ([#2495](https://github.com/jaegertracing/jaeger/pull/2495), [@joe-elliott](https://github.com/joe-elliott))\n* Fix failing ServerInUseHostPort test on MacOS ([#2477](https://github.com/jaegertracing/jaeger/pull/2477), [@albertteoh](https://github.com/albertteoh))\n* Fix unmarshalling in OTEL badger ([#2488](https://github.com/jaegertracing/jaeger/pull/2488), [@pavolloffay](https://github.com/pavolloffay))\n* Improve UI placeholder message ([#2487](https://github.com/jaegertracing/jaeger/pull/2487), [@yurishkuro](https://github.com/yurishkuro))\n* Translate OTEL instrumentation library to ES DB model ([#2484](https://github.com/jaegertracing/jaeger/pull/2484), [@pavolloffay](https://github.com/pavolloffay))\n* Add partial retry capability to OTEL ES exporter. ([#2456](https://github.com/jaegertracing/jaeger/pull/2456), [@pavolloffay](https://github.com/pavolloffay))\n* Log deprecation warning only when deprecated flags are set ([#2479](https://github.com/jaegertracing/jaeger/pull/2479), [@pavolloffay](https://github.com/pavolloffay))\n* Clean-up Badger's trace-not-found check ([#2481](https://github.com/jaegertracing/jaeger/pull/2481), [@yurishkuro](https://github.com/yurishkuro))\n* Run the jaeger-agent as a non-root user by default ([#2466](https://github.com/jaegertracing/jaeger/pull/2466), [@chgl](https://github.com/chgl))\n* Regenerate certificates to use SANs instead of Common Name ([#2461](https://github.com/jaegertracing/jaeger/pull/2461), [@albertteoh](https://github.com/albertteoh))\n* Support custom port in cassandra schema creation ([#2472](https://github.com/jaegertracing/jaeger/pull/2472), [@MarianZoll](https://github.com/MarianZoll))\n* Consolidated OTel ES IndexNameProviders ([#2458](https://github.com/jaegertracing/jaeger/pull/2458), [@joe-elliott](https://github.com/joe-elliott))\n* Add positive confirmation that Agent made a connection to Collector (… ([#2423](https://github.com/jaegertracing/jaeger/pull/2423), [@BernardTolosajr](https://github.com/BernardTolosajr))\n* Propagate TraceNotFound error from grpc storage plugins ([#2455](https://github.com/jaegertracing/jaeger/pull/2455), [@joe-elliott](https://github.com/joe-elliott))\n* Use new ES reader implementation in OTEL ([#2441](https://github.com/jaegertracing/jaeger/pull/2441), [@pavolloffay](https://github.com/pavolloffay))\n* Updated grpc-go to v1.29.1 ([#2445](https://github.com/jaegertracing/jaeger/pull/2445), [@jpkrohling](https://github.com/jpkrohling))\n* Remove olivere elastic client from OTEL ([#2448](https://github.com/jaegertracing/jaeger/pull/2448), [@pavolloffay](https://github.com/pavolloffay))\n* Use queue retry per exporter ([#2444](https://github.com/jaegertracing/jaeger/pull/2444), [@pavolloffay](https://github.com/pavolloffay))\n* Add context.Context to WriteSpan ([#2436](https://github.com/jaegertracing/jaeger/pull/2436), [@yurishkuro](https://github.com/yurishkuro))\n* Fix mutex unlock in storage exporters ([#2442](https://github.com/jaegertracing/jaeger/pull/2442), [@pavolloffay](https://github.com/pavolloffay))\n* Add Grafana integration example ([#2408](https://github.com/jaegertracing/jaeger/pull/2408), [@fktkrt](https://github.com/fktkrt))\n* Fix TLS flags settings in jaeger OTEL receiver ([#2438](https://github.com/jaegertracing/jaeger/pull/2438), [@pavolloffay](https://github.com/pavolloffay))\n* Add context to dependencies endpoint ([#2434](https://github.com/jaegertracing/jaeger/pull/2434), [@yoave23](https://github.com/yoave23))\n* Fix error equals ([#2429](https://github.com/jaegertracing/jaeger/pull/2429), [@albertteoh](https://github.com/albertteoh))\n\n### UI Changes\n\n* UI pinned to version 1.11.0. The changelog is available here [v1.11.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1110-september-28-2020)\n\n1.19.2 (2020-08-26)\n-------------------\n\nUpgrade to a working UI version before React refactoring.\n\n\n1.19.1 (2020-08-26)\n-------------------\n\nRevert UI back to 1.9 due to a bug https://github.com/jaegertracing/jaeger-ui/issues/628\n\n\n1.19.0 (2020-08-26)\n-------------------\n\n### Known Issues\n\nThe pull request [#2297](https://github.com/jaegertracing/jaeger/pull/2297) aimed to add TLS support for the gRPC Query server but the flag registration is missing, so that this feature can't be used at the moment. A fix is planned for the next Jaeger version (1.20).\n\n### Backend Changes\n\n#### New Features\n\n* Reload TLS certificates on change ([#2389](https://github.com/jaegertracing/jaeger/pull/2389), [@pavolloffay](https://github.com/pavolloffay))\n* Add --kafka.producer.batch-min-messages collector flag ([#2371](https://github.com/jaegertracing/jaeger/pull/2371), [@prymitive](https://github.com/prymitive))\n* Make UDP socket buffer size configurable ([#2336](https://github.com/jaegertracing/jaeger/pull/2336), [@kbarukhov](https://github.com/kbarukhov))\n* Enable batch and queued retry processors by default ([#2330](https://github.com/jaegertracing/jaeger/pull/2330), [@pavolloffay](https://github.com/pavolloffay))\n* Add trace anonymizer prototype ([#2328](https://github.com/jaegertracing/jaeger/pull/2328), [@yurishkuro](https://github.com/yurishkuro))\n* Add native OTEL ES exporter ([#2295](https://github.com/jaegertracing/jaeger/pull/2295), [@pavolloffay](https://github.com/pavolloffay))\n* Define busy error type in processor ([#2314](https://github.com/jaegertracing/jaeger/pull/2314), [@pavolloffay](https://github.com/pavolloffay))\n* Use gRPC instead of tchannel in hotrod ([#2307](https://github.com/jaegertracing/jaeger/pull/2307), [@pavolloffay](https://github.com/pavolloffay))\n* TLS support for gRPC Query server ([#2297](https://github.com/jaegertracing/jaeger/pull/2297), [@jan25](https://github.com/jan25))\n\n#### Bug fixes, Minor Improvements\n\n* Check missing server URL in ES OTEL client ([#2411](https://github.com/jaegertracing/jaeger/pull/2411), [@pavolloffay](https://github.com/pavolloffay))\n* Fix Elasticsearch version in ES OTEL writer ([#2409](https://github.com/jaegertracing/jaeger/pull/2409), [@pavolloffay](https://github.com/pavolloffay))\n* Fix go vet warnings on Go 1.15 ([#2401](https://github.com/jaegertracing/jaeger/pull/2401), [@prymitive](https://github.com/prymitive))\n* Add new Elasticsearch reader implementation ([#2364](https://github.com/jaegertracing/jaeger/pull/2364), [@pavolloffay](https://github.com/pavolloffay))\n* Only add the collector port if it was not explicitly set ([#2396](https://github.com/jaegertracing/jaeger/pull/2396), [@joe-elliott](https://github.com/joe-elliott))\n* Fix panic in collector when Zipkin server is shutdown  ([#2392](https://github.com/jaegertracing/jaeger/pull/2392), [@Sreevani871](https://github.com/Sreevani871))\n* Update validity of TLS certificates to 3650 days ([#2390](https://github.com/jaegertracing/jaeger/pull/2390), [@rjs211](https://github.com/rjs211))\n* Added span and trace id to hotrod logs ([#2384](https://github.com/jaegertracing/jaeger/pull/2384), [@joe-elliott](https://github.com/joe-elliott))\n* Jaeger agent logs \"0\" whenever sampling strategies are requested ([#2382](https://github.com/jaegertracing/jaeger/pull/2382), [@jpkrohling](https://github.com/jpkrohling))\n* Fix shutdown order for collector components ([#2381](https://github.com/jaegertracing/jaeger/pull/2381), [@Sreevani871](https://github.com/Sreevani871))\n* Serve jquery-3.1.1.min.js locally ([#2378](https://github.com/jaegertracing/jaeger/pull/2378), [@chaseSpace](https://github.com/chaseSpace))\n* Use a single shared set of CA, client & server keys/certs for testing ([#2343](https://github.com/jaegertracing/jaeger/pull/2343), [@rjs211](https://github.com/rjs211))\n* fix null pointer in jaeger-spark-dependencies ([#2327](https://github.com/jaegertracing/jaeger/pull/2327), [@moolen](https://github.com/moolen))\n* Rename ARCH to TARGETARCH for multi platform build by docker buildx ([#2320](https://github.com/jaegertracing/jaeger/pull/2320), [@morlay](https://github.com/morlay))\n* Mask passwords when written as json ([#2302](https://github.com/jaegertracing/jaeger/pull/2302), [@objectiser](https://github.com/objectiser))\n\n### UI Changes\n\n* UI pinned to version 1.10.0. The changelog is available here [v1.10.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v1100-august-25-2020)\n\n1.18.1 (2020-06-19)\n------------------\n\n### Backend Changes\n\n#### Security Fixes\n\n* CVE-2020-10750: jaegertracing/jaeger: credentials leaked to container logs ([@chlunde](https://github.com/chlunde))\n\n#### ⛔ Breaking Changes\n\n#### New Features\n* Add ppc64le support ([#2293](https://github.com/jaegertracing/jaeger/pull/2293), [@Siddhesh-Ghadi](https://github.com/Siddhesh-Ghadi))\n* Expose option to enable TLS when sniffing an Elasticsearch Cluster ([#2263](https://github.com/jaegertracing/jaeger/pull/2263), [@jennynilsen](https://github.com/jennynilsen))\n* Enable OTEL receiver by default ([#2279](https://github.com/jaegertracing/jaeger/pull/2279), [@pavolloffay](https://github.com/pavolloffay))\n* Add Badger OTEL exporter ([#2269](https://github.com/jaegertracing/jaeger/pull/2269), [@pavolloffay](https://github.com/pavolloffay))\n* Add all-in-one OTEL component ([#2262](https://github.com/jaegertracing/jaeger/pull/2262), [@pavolloffay](https://github.com/pavolloffay))\n* Add arm64 support for binaries and docker images ([#2176](https://github.com/jaegertracing/jaeger/pull/2176), [@MrXinWang](https://github.com/MrXinWang))\n* Add Zipkin OTEL receiver ([#2247](https://github.com/jaegertracing/jaeger/pull/2247), [@pavolloffay](https://github.com/pavolloffay))\n\n#### Bug fixes, Minor Improvements\n* Remove experimental flag from rollover ([#2290](https://github.com/jaegertracing/jaeger/pull/2290), [@pavolloffay](https://github.com/pavolloffay))\n* Move ES dependencies index mapping to JSON template file ([#2285](https://github.com/jaegertracing/jaeger/pull/2285), [@pavolloffay](https://github.com/pavolloffay))\n* Bump go-plugin to 1.3 ([#2261](https://github.com/jaegertracing/jaeger/pull/2261), [@yurishkuro](https://github.com/yurishkuro))\n* Ignore chmod events on UI config watcher. ([#2254](https://github.com/jaegertracing/jaeger/pull/2254), [@rubenvp8510](https://github.com/rubenvp8510))\n* Normalize CLI flags to use host:port addresses ([#2212](https://github.com/jaegertracing/jaeger/pull/2212), [@ohdearaugustin](https://github.com/ohdearaugustin))\n* Add kafka receiver flags to ingester ([#2251](https://github.com/jaegertracing/jaeger/pull/2251), [@pavolloffay](https://github.com/pavolloffay))\n* Configure Jaeger receiver and exporter by flags ([#2241](https://github.com/jaegertracing/jaeger/pull/2241), [@pavolloffay](https://github.com/pavolloffay))\n\n### UI Changes\n\n1.18.0 (2020-05-14)\n------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n* Remove Tchannel between agent and collector ([#2115](https://github.com/jaegertracing/jaeger/pull/2115), [#2112](https://github.com/jaegertracing/jaeger/pull/2112), [@pavolloffay](https://github.com/pavolloffay))\n\n    Remove `Tchannel` port (14267) from collector and `Tchannel` reporter from agent.\n\n    These flags were removed from agent:\n    ```\n    --collector.host-port\n    --reporter.tchannel.discovery.conn-check-timeout\n    --reporter.tchannel.discovery.min-peers\n    --reporter.tchannel.host-port\n    --reporter.tchannel.report-timeout\n    ```\n\n    These flags were removed from collector:\n    ```\n    --collector.port\n    ```\n\n* Normalize CLI flags to use host:port addresses ([#1827](https://github.com/jaegertracing/jaeger/pull/1827), [@annanay25](https://github.com/annanay25))\n\n  Flags previously accepting listen addresses in any other format have been deprecated:\n\n  * `collector.port` is superseded by `collector.tchan-server.host-port`\n  * `collector.http-port` is superseded by `collector.http-server.host-port`\n  * `collector.grpc-port` is superseded by `collector.grpc-server.host-port`\n  * `collector.zipkin.http-port` is superseded by `collector.zipkin.host-port`\n  * `admin-http-port` is superseded by `admin.http.host-port`\n\n#### New Features\n\n* Add grpc storage plugin OTEL exporter ([#2229](https://github.com/jaegertracing/jaeger/pull/2229), [@pavolloffay](https://github.com/pavolloffay))\n* Add OTEL ingester component ([#2225](https://github.com/jaegertracing/jaeger/pull/2225), [@pavolloffay](https://github.com/pavolloffay))\n* Add Kafka OTEL receiver/ingester ([#2221](https://github.com/jaegertracing/jaeger/pull/2221), [@pavolloffay](https://github.com/pavolloffay))\n* Create Jaeger OTEL agent component  ([#2216](https://github.com/jaegertracing/jaeger/pull/2216), [@pavolloffay](https://github.com/pavolloffay))\n* Merge hardcoded/default configuration with OTEL config file ([#2211](https://github.com/jaegertracing/jaeger/pull/2211), [@pavolloffay](https://github.com/pavolloffay))\n* Support periodic refresh of sampling strategies ([#2188](https://github.com/jaegertracing/jaeger/pull/2188), [@defool](https://github.com/defool))\n* Add Elasticsearch OTEL exporter ([#2140](https://github.com/jaegertracing/jaeger/pull/2140), [@pavolloffay](https://github.com/pavolloffay))\n* Add Cassandra OTEL exporter ([#2139](https://github.com/jaegertracing/jaeger/pull/2139), [@pavolloffay](https://github.com/pavolloffay))\n* Add Kafka OTEL storage exporter ([#2135](https://github.com/jaegertracing/jaeger/pull/2135), [@pavolloffay](https://github.com/pavolloffay))\n* Clock skew config ([#2119](https://github.com/jaegertracing/jaeger/pull/2119), [@joe-elliott](https://github.com/joe-elliott))\n* Introduce OpenTelemetry collector ([#2086](https://github.com/jaegertracing/jaeger/pull/2086), [@pavolloffay](https://github.com/pavolloffay))\n* Support regex tags search for Elasticseach backend ([#2049](https://github.com/jaegertracing/jaeger/pull/2049), [@annanay25](https://github.com/annanay25))\n\n#### Bug fixes, Minor Improvements\n\n* Do not skip service/operation indexing for firehose spans ([#2242](https://github.com/jaegertracing/jaeger/pull/2242), [@yurishkuro](https://github.com/yurishkuro))\n* Add build information to OTEL binaries ([#2237](https://github.com/jaegertracing/jaeger/pull/2237), [@pavolloffay](https://github.com/pavolloffay))\n* Enable service default sampling param ([#2230](https://github.com/jaegertracing/jaeger/pull/2230), [@defool](https://github.com/defool))\n* Add Jaeger OTEL agent to docker image upload ([#2227](https://github.com/jaegertracing/jaeger/pull/2227), [@ning2008wisc](https://github.com/ning2008wisc))\n* Support adding process tags in OTEL via env variable ([#2220](https://github.com/jaegertracing/jaeger/pull/2220), [@pavolloffay](https://github.com/pavolloffay))\n* Bump OTEL version and update exporters to use new API ([#2196](https://github.com/jaegertracing/jaeger/pull/2196), [@pavolloffay](https://github.com/pavolloffay))\n* Support sampling strategies file flag in OTEL collector ([#2195](https://github.com/jaegertracing/jaeger/pull/2195), [@pavolloffay](https://github.com/pavolloffay))\n* Add zipkin receiver to OTEL collector ([#2181](https://github.com/jaegertracing/jaeger/pull/2181), [@pavolloffay](https://github.com/pavolloffay))\n* Add Dockerfile for OTEL collector and publish latest tag ([#2167](https://github.com/jaegertracing/jaeger/pull/2167), [@pavolloffay](https://github.com/pavolloffay))\n* Run OTEL collector without configuration file ([#2148](https://github.com/jaegertracing/jaeger/pull/2148), [@pavolloffay](https://github.com/pavolloffay))\n* Update gocql to support AWS MCS ([#2133](https://github.com/jaegertracing/jaeger/pull/2133), [@johanneswuerbach](https://github.com/johanneswuerbach))\n* Return appropriate gRPC errors/codes to indicate request status ([#2132](https://github.com/jaegertracing/jaeger/pull/2132), [@yurishkuro](https://github.com/yurishkuro))\n* Remove tchannel port from dockerfile and test ([#2118](https://github.com/jaegertracing/jaeger/pull/2118), [@pavolloffay](https://github.com/pavolloffay))\n* Remove tchannel between agent and collector ([#2115](https://github.com/jaegertracing/jaeger/pull/2115), [@pavolloffay](https://github.com/pavolloffay))\n* Move all tchannel packages to a single top level package ([#2112](https://github.com/jaegertracing/jaeger/pull/2112), [@pavolloffay](https://github.com/pavolloffay))\n\n### UI Changes\n\n* UI pinned to version 1.9.0. The changelog is available here [v1.9.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v190-may-14-2020)\n\n1.17.1 (2020-03-13)\n------------------\n\n#### Bug fixes, Minor Improvements\n\n* Fix enable Kafka TLS when TLS auth is specified [#2107](https://github.com/jaegertracing/jaeger/pull/2107), [@pavolloffay](https://github.com/pavoloffay))\n* Migrate project to go modules [#2098](https://github.com/jaegertracing/jaeger/pull/2098), [@pavolloffay](https://github.com/pavoloffay))\n* Do not skip service/operation indexing for firehose spans [#2090](https://github.com/jaegertracing/jaeger/pull/2090), [@yurishkuro](https://github.com/yurishkuro))\n* Close the span writer on main ([#2096](https://github.com/jaegertracing/jaeger/pull/2096), [@jpkrohling](https://github.com/jpkrohling))\n* Improved graceful shutdown - Collector ([#2076](https://github.com/jaegertracing/jaeger/pull/2076), [@jpkrohling](https://github.com/jpkrohling))\n* Improved graceful shutdown - Agent ([#2031](https://github.com/jaegertracing/jaeger/pull/2031), [@jpkrohling](https://github.com/jpkrohling))\n\n### UI Changes\n\n* UI pinned to version 1.8.0. The changelog is available here [v1.8.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v180-march-12-2020)\n\n1.17.0 (2020-02-24)\n------------------\n\n### Backend Changes\n\n#### New Features\n\n* [tracegen] Add service name as a command line option ([#2080](https://github.com/jaegertracing/jaeger/pull/2080), [@kevinearls](https://github.com/kevinearls))\n* Allow the Configuration of Additional Headers on the Jaeger Query HTTP API ([#2056](https://github.com/jaegertracing/jaeger/pull/2056), [@joe-elliott](https://github.com/joe-elliott))\n* Warn about time adjustment in tags ([#2052](https://github.com/jaegertracing/jaeger/pull/2052), [@bobrik](https://github.com/bobrik))\n* Add CLI flags for Kafka batching params ([#2047](https://github.com/jaegertracing/jaeger/pull/2047), [@apm-opentt](https://github.com/apm-opentt))\n* Added support for dynamic queue sizes ([#1985](https://github.com/jaegertracing/jaeger/pull/1985), [@jpkrohling](https://github.com/jpkrohling))\n* [agent] Process data loss stats from clients ([#2010](https://github.com/jaegertracing/jaeger/pull/2010), [@yurishkuro](https://github.com/yurishkuro))\n* Add /api/sampling endpoint in collector ([#1990](https://github.com/jaegertracing/jaeger/pull/1990), [@RickyRajinder](https://github.com/RickyRajinder))\n* Add basic authentication to Kafka storage ([#1983](https://github.com/jaegertracing/jaeger/pull/1983), [@chandresh-pancholi](https://github.com/chandresh-pancholi))\n* Make operation_strategies part also be part of default_strategy  ([#1749](https://github.com/jaegertracing/jaeger/pull/1749), [@rutgerbrf](https://github.com/rutgerbrf))\n\n#### Bug fixes, Minor Improvements\n\n* Upgrade gRPC to ^1.26 ([#2077](https://github.com/jaegertracing/jaeger/pull/2077), [@yurishkuro](https://github.com/yurishkuro))\n* Remove pkg/errors from dependencies ([#2073](https://github.com/jaegertracing/jaeger/pull/2073), [@yurishkuro](https://github.com/yurishkuro))\n* Update dependencies, pin grpc<1.27 ([#2072](https://github.com/jaegertracing/jaeger/pull/2072), [@yurishkuro](https://github.com/yurishkuro))\n* Refactor collector mains ([#2060](https://github.com/jaegertracing/jaeger/pull/2060), [@jpkrohling](https://github.com/jpkrohling))\n* Clarify that \"kafka\" is not a real storage backend ([#2066](https://github.com/jaegertracing/jaeger/pull/2066), [@yurishkuro](https://github.com/yurishkuro))\n* Added -trimpath option to go build ([#2064](https://github.com/jaegertracing/jaeger/pull/2064), [@kadern0](https://github.com/kadern0))\n* Use memory size flag to activate dyn queue size feature ([#2059](https://github.com/jaegertracing/jaeger/pull/2059), [@jpkrohling](https://github.com/jpkrohling))\n* Add before you push to the queue to prevent race condition on size ([#2044](https://github.com/jaegertracing/jaeger/pull/2044), [@joe-elliott](https://github.com/joe-elliott))\n* Count received batches from conforming clients ([#2030](https://github.com/jaegertracing/jaeger/pull/2030), [@yurishkuro](https://github.com/yurishkuro))\n* [agent] Do not increment data loss counters on the first client batch ([#2028](https://github.com/jaegertracing/jaeger/pull/2028), [@yurishkuro](https://github.com/yurishkuro))\n* Allow raw port numbers for UDP servers ([#2025](https://github.com/jaegertracing/jaeger/pull/2025), [@yurishkuro](https://github.com/yurishkuro))\n* Publish tracegen ([#2022](https://github.com/jaegertracing/jaeger/pull/2022), [@jpkrohling](https://github.com/jpkrohling))\n* Build binaries for Linux on IBM Z / s390x architecture ([#1982](https://github.com/jaegertracing/jaeger/pull/1982), [@prankkelkar](https://github.com/prankkelkar))\n* Admin/Query: Log the real port instead of the provided one to enable the use of port 0 ([#2002](https://github.com/jaegertracing/jaeger/pull/2002), [@ChadiEM](https://github.com/ChadiEM))\n* Split agent's HTTP server and handler ([#1996](https://github.com/jaegertracing/jaeger/pull/1996), [@yurishkuro](https://github.com/yurishkuro))\n* Clean-up collector handlers builder ([#1991](https://github.com/jaegertracing/jaeger/pull/1991), [@yurishkuro](https://github.com/yurishkuro))\n* Added 'resize' operation to BoundedQueue ([#1948](https://github.com/jaegertracing/jaeger/pull/1949), [@jpkrohling](https://github.com/jpkrohling))\n* Add common TLS configuration ([#1838](https://github.com/jaegertracing/jaeger/pull/1838), [@backjo](https://github.com/backjo))\n\n### UI Changes\n\n* UI pinned to version 1.7.0. The changelog is available here [v1.7.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v170-february-21-2020)\n\n1.16.0 (2019-12-17)\n------------------\n\n### Backend Changes\n\n#### ⛔ Breaking Changes\n\n##### List of service operations can be classified by span kinds ([#1943](https://github.com/jaegertracing/jaeger/pull/1943), [#1942](https://github.com/jaegertracing/jaeger/pull/1942), [#1937](https://github.com/jaegertracing/jaeger/pull/1937), [@guo0693](https://github.com/guo0693))\n\n* Endpoint changes:\n    * Both Http & gRPC servers now take new optional parameter `spanKind` in addition to `service`. When spanKind\n     is absent or empty, operations from all kinds of spans will be returned.\n    * Instead of returning a list of string, both Http & gRPC servers return a list of operation struct. Please\n    update your client code to process the new response. Example response:\n        ```\n        curl 'http://localhost:6686/api/operations?service=UserService&spanKind=server' | jq\n        {\n            \"data\": [{\n                \"name\": \"UserService::getExtendedUser\",\n                \"spanKind\": \"server\"\n            },\n            {\n                \"name\": \"UserService::getUserProfile\",\n                \"spanKind\": \"server\"\n            }],\n            \"total\": 2,\n            \"limit\": 0,\n            \"offset\": 0,\n            \"errors\": null\n        }\n        ```\n    * The legacy http endpoint stay untouched:\n        ```\n        /services/{%s}/operations\n        ```\n* Storage plugin changes:\n    * Memory updated to support spanKind on write & read, no migration is required.\n    * [Badger](https://github.com/jaegertracing/jaeger/issues/1922) & [ElasticSearch](https://github.com/jaegertracing/jaeger/issues/1923)\n    to be implemented:\n    For now `spanKind` will be set as empty string during read & write, only `name` will be valid operation name.\n    * Cassandra updated to support spanKind on write & read ([#1937](https://github.com/jaegertracing/jaeger/pull/1937), [@guo0693](https://github.com/guo0693)):\n        If you don't run the migration script, nothing will break, the system will use the old table\n        `operation_names` and set empty `spanKind` in the response.\n        Steps to get the updated functionality:\n        1.  You will need to run the command below on the host where you can use `cqlsh` to connect to Cassandra:\n            ```\n            KEYSPACE=jaeger_v1 CQL_CMD='cqlsh host 9042 -u test_user -p test_password --request-timeout=3000'\n            bash ./v002tov003.sh\n            ```\n            The script will create new table `operation_names_v2` and migrate data from the old table.\n            `spanKind` column will be empty for those data.\n            At the end, it will ask you whether you want to drop the old table or not.\n        2. Restart ingester & query services so that they begin to use the new table\n\n##### Trace and Span IDs are always padded to 32 or 16 hex characters with leading zeros ([#1956](https://github.com/jaegertracing/jaeger/pull/1956), [@yurishkuro](https://github.com/yurishkuro))\n\nPreviously, Jaeger backend always rendered trace and span IDs as  the shortest possible hex string, e.g. an ID\nwith numeric value 255 would be rendered as a string `ff`. This change makes the IDs to always render as 16 or 32\ncharacters long hex string, e.g. the same id=255 would render as `00000000000000ff`. It mostly affects how UI\ndisplays the IDs, the URLs, and the JSON returned from `jaeger-query` service.\n\nMotivation: Among randomly generated and uniformly distributed trace IDs, only 1/16th of them start with 0\nfollowed by a significant digit, 1/256th start with two 0s, and so on in decreasing geometric progression.\nTherefore, trimming the leading 0s is a very modest optimization on the size of the data being transmitted or stored.\n\nHowever, trimming 0s leads to ambiguities when the IDs are used as correlations with other monitoring systems,\nsuch as logging, that treat the IDs as opaque strings and cannot establish the equivalence between padded and\nunpadded IDs. It is also incompatible with W3C Trace Context and Zipkin B3 formats, both of which include all\nleading 0s, so an application instrumented with OpenTelemetry SDKs may be logging different trace ID strings\nthan application instrumented with Jaeger SDKs (related issue #1657).\n\nOverall, the change is backward compatible:\n  * links with non-padded IDs in the UI will still work\n  * data stored in Elasticsearch (where IDs are represented as strings) is still readable\n\nHowever, some custom integration that rely on exact string matches of trace IDs may be broken.\n\n##### Change default rollover conditions to 2 days ([#1963](https://github.com/jaegertracing/jaeger/pull/1963), [@pavolloffay](https://github.com/pavolloffay))\n\nChange default rollover conditions from 7 days to 2 days.\n\nGiven that by default Jaeger uses daily indices and some organizations do not keep data longer than 7 days\nthe default of 7 days seems unreasonable - it might result in a too big index and\nrunning curator would immediately remove the old index.\n\n#### New Features\n\n* Support collector tags, similar to agent tags ([#1854](https://github.com/jaegertracing/jaeger/pull/1854), [@radekg](https://github.com/radekg))\n* Support insecure TLS and only CA cert for Elasticsearch ([#1918](https://github.com/jaegertracing/jaeger/pull/1918), [@pavolloffay](https://github.com/pavolloffay))\n* Allow tracer config via env vars ([#1919](https://github.com/jaegertracing/jaeger/pull/1919), [@yurishkuro](https://github.com/yurishkuro))\n* Allow turning off tags/logs indexing in Cassandra ([#1915](https://github.com/jaegertracing/jaeger/pull/1915), [@joe-elliott](https://github.com/joe-elliott))\n* Blacklisting/Whitelisting tags for Cassandra indexing  ([#1904](https://github.com/jaegertracing/jaeger/pull/1904), [@joe-elliott](https://github.com/joe-elliott))\n\n#### Bug fixes, Minor Improvements\n\n* Support custom basepath in HotROD ([#1894](https://github.com/jaegertracing/jaeger/pull/1894), [@jan25](https://github.com/jan25))\n* Deprecate tchannel reporter flags ([#1978](https://github.com/jaegertracing/jaeger/pull/1978), [@objectiser](https://github.com/objectiser))\n* Do not truncate tags in Elasticsearch ([#1970](https://github.com/jaegertracing/jaeger/pull/1970), [@pavolloffay](https://github.com/pavolloffay))\n* Export SaveSpan to enable multiplexing ([#1968](https://github.com/jaegertracing/jaeger/pull/1968), [@albertteoh](https://github.com/albertteoh))\n* Make rollover init step idempotent ([#1964](https://github.com/jaegertracing/jaeger/pull/1964), [@pavolloffay](https://github.com/pavolloffay))\n* Update python urllib3 version required by curator ([#1965](https://github.com/jaegertracing/jaeger/pull/1965), [@pavolloffay](https://github.com/pavolloffay))\n* Allow changing max log level for gRPC storage plugins ([#1962](https://github.com/jaegertracing/jaeger/pull/1962), [@yyyogev](https://github.com/yyyogev))\n* Fix the bug that operation_name table can not be init more than once ([#1961](https://github.com/jaegertracing/jaeger/pull/1961), [@guo0693](https://github.com/guo0693))\n* Improve migration script ([#1946](https://github.com/jaegertracing/jaeger/pull/1946), [@guo0693](https://github.com/guo0693))\n* Fix order of the returned results from badger backend.  ([#1939](https://github.com/jaegertracing/jaeger/pull/1939), [@burmanm](https://github.com/burmanm))\n* Update python pathlib to pathlib2 ([#1930](https://github.com/jaegertracing/jaeger/pull/1930), [@objectiser](https://github.com/objectiser))\n* Use proxy env vars if they're configured ([#1910](https://github.com/jaegertracing/jaeger/pull/1910), [@zoidbergwill](https://github.com/zoidbergwill))\n\n### UI Changes\n\n* UI pinned to version 1.6.0. The changelog is available here [v1.6.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v160-december-16-2019)\n\n1.15.1 (2019-11-07)\n------------------\n\n##### Bug fixes, Minor Improvements\n\n* Build platform binaries as part of CI ([#1909](https://github.com/jaegertracing/jaeger/pull/1909), [@yurishkuro](https://github.com/yurishkuro))\n* Upgrade and fix dependencies ([#1907](https://github.com/jaegertracing/jaeger/pull/1907), [@yurishkuro](https://github.com/yurishkuro))\n\n\n1.15.0 (2019-11-07)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n* The default value for the Ingester's flag `ingester.deadlockInterval` has been changed to `0` ([#1868](https://github.com/jaegertracing/jaeger/pull/1868), [@jpkrohling](https://github.com/jpkrohling))\n\n  With the new default, the ingester won't `panic` if there are no messages for the last minute. To restore the previous behavior, set the flag's value to `1m`.\n\n* Mark `--collector.grpc.tls.client.ca` flag as deprecated for jaeger-collector. ([#1840](https://github.com/jaegertracing/jaeger/pull/1840), [@yurishkuro](https://github.com/yurishkuro))\n\n  The deprecated flag will still work until being removed, it's recommended to use `--collector.grpc.tls.client-ca` instead.\n\n##### New Features\n\n* Support TLS for Kafka ([#1414](https://github.com/jaegertracing/jaeger/pull/1414), [@MichaHoffmann](https://github.com/MichaHoffmann))\n* Add ack and compression parameters for Kafka #1359 ([#1712](https://github.com/jaegertracing/jaeger/pull/1712), [@chandresh-pancholi](https://github.com/chandresh-pancholi))\n* Propagate the bearer token to the gRPC plugin server ([#1822](https://github.com/jaegertracing/jaeger/pull/1822), [@radekg](https://github.com/radekg))\n* Add Truncate and ReadOnly options for badger ([#1842](https://github.com/jaegertracing/jaeger/pull/1842), [@burmanm](https://github.com/burmanm))\n\n##### Bug fixes, Minor Improvements\n\n* Use correct context on ES search methods ([#1850](https://github.com/jaegertracing/jaeger/pull/1850), [@rubenvp8510](https://github.com/rubenvp8510))\n* Handling of expected error codes coming from grpc storage plugins #1741 ([#1814](https://github.com/jaegertracing/jaeger/pull/1814), [@chandresh-pancholi](https://github.com/chandresh-pancholi))\n* Fix ordering of indexScanKeys after TraceID parsing ([#1809](https://github.com/jaegertracing/jaeger/pull/1809), [@burmanm](https://github.com/burmanm))\n* Small memory optimizations in badger write-path ([#1771](https://github.com/jaegertracing/jaeger/pull/1771), [@burmanm](https://github.com/burmanm))\n* Set an empty value when a default env var value is missing ([#1777](https://github.com/jaegertracing/jaeger/pull/1777), [@jpkrohling](https://github.com/jpkrohling))\n* Decouple storage dependencies and bump Go to 1.13.x ([#1886](https://github.com/jaegertracing/jaeger/pull/1886), [@yurishkuro](https://github.com/yurishkuro))\n* Update gopkg.in/yaml.v2 dependency to v2.2.4 ([#1865](https://github.com/jaegertracing/jaeger/pull/1865), [@objectiser](https://github.com/objectiser))\n* Upgrade jaeger-client 2.19 and jaeger-lib 2.2 and prom client 1.x ([#1810](https://github.com/jaegertracing/jaeger/pull/1810), [@yurishkuro](https://github.com/yurishkuro))\n* Unpin grpc version and use serviceConfig to set the load balancer  ([#1786](https://github.com/jaegertracing/jaeger/pull/1786), [@guanw](https://github.com/guanw))\n\n#### UI Changes\n\n* UI pinned to version 1.5.0. The changelog is available here [v1.5.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v150-november-4-2019)\n\n1.14.0 (2019-09-02)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n* Create ES index templates instead of indices ([#1627](https://github.com/jaegertracing/jaeger/pull/1627), [@pavolloffay](https://github.com/pavolloffay))\n\n  This can break existing Elasticsearch deployments if security policies are applied.\n  For instance Jaeger `X-Pack` configuration now requires permission to create index templates - `manage_index_templates`.\n\n##### New Features\n\n* Add Elasticsearch version configuration to rollover script ([#1769](https://github.com/jaegertracing/jaeger/pull/1769), [@pavolloffay](https://github.com/pavolloffay))\n* Add Elasticsearch version flag ([#1753](https://github.com/jaegertracing/jaeger/pull/1753), [@pavolloffay](https://github.com/pavolloffay))\n* Add Elasticsearch 7 support ([#1690](https://github.com/jaegertracing/jaeger/pull/1690), [@gregoryfranklin](https://github.com/gregoryfranklin))\n\n  The index mappings in Elasticsearch 7 are not backwards compatible with the older versions.\n  Therefore using Elasticsearch 7 with data created with older version would not work.\n  Elasticsearch 6.8 supports 7.x, 6.x, 5.x compatible mappings. The upgrade has to be done\n  first to ES 6.8, then apply data migration or wait until old daily indices are removed (this requires\n  to start Jaeger with `--es.version=7` to force using ES 7.x mappings for newly created indices).\n\n  Jaeger by default uses Elasticsearch ping endpoint (`/`) to derive the version which is used\n  for index mappings selection. The version can be overridden by flag `--es.version`.\n\n* Support for Zipkin Protobuf spans over HTTP ([#1695](https://github.com/jaegertracing/jaeger/pull/1695), [@jan25](https://github.com/jan25))\n* Added support for hot reload of UI config ([#1688](https://github.com/jaegertracing/jaeger/pull/1688), [@jpkrohling](https://github.com/jpkrohling))\n* Added base Grafana dashboard and Alert rules ([#1745](https://github.com/jaegertracing/jaeger/pull/1745), [@jpkrohling](https://github.com/jpkrohling))\n* Add the jaeger-mixin for monitoring ([#1668](https://github.com/jaegertracing/jaeger/pull/1668), [@gouthamve](https://github.com/gouthamve))\n* Added flags for driving cassandra connection compression through config ([#1675](https://github.com/jaegertracing/jaeger/pull/1675), [@sagaranand015](https://github.com/sagaranand015))\n* Support index cleaner for rollover indices and add integration tests ([#1689](https://github.com/jaegertracing/jaeger/pull/1689), [@pavolloffay](https://github.com/pavolloffay))\n* Add client TLS auth to gRPC reporter ([#1591](https://github.com/jaegertracing/jaeger/pull/1591), [@tcolgate](https://github.com/tcolgate))\n* Collector kafka producer protocol version config ([#1658](https://github.com/jaegertracing/jaeger/pull/1658), [@marqc](https://github.com/marqc))\n* Configurable kafka protocol version for msg consuming by jaeger ingester ([#1640](https://github.com/jaegertracing/jaeger/pull/1640), [@marqc](https://github.com/marqc))\n* Use credentials when describing keyspaces in cassandra schema builder ([#1655](https://github.com/jaegertracing/jaeger/pull/1655), [@MiLk](https://github.com/MiLk))\n* Add connect-timeout for Cassandra ([#1647](https://github.com/jaegertracing/jaeger/pull/1647), [@sagaranand015](https://github.com/sagaranand015))\n\n##### Bug fixes, Minor Improvements\n\n* Fix gRPC over cmux and add unit tests ([#1758](https://github.com/jaegertracing/jaeger/pull/1758), [@yurishkuro](https://github.com/yurishkuro))\n* Add CA certificates to agent image ([#1764](https://github.com/jaegertracing/jaeger/pull/1764), [@yurishkuro](https://github.com/yurishkuro))\n* Fix badger merge-join algorithm to correctly filter indexes ([#1721](https://github.com/jaegertracing/jaeger/pull/1721), [@burmanm](https://github.com/burmanm))\n* Change Zipkin CORS origins and headers to comma separated list ([#1556](https://github.com/jaegertracing/jaeger/pull/1556), [@JonasVerhofste](https://github.com/JonasVerhofste))\n* Added null guards to 'Process' when processing an incoming span ([#1723](https://github.com/jaegertracing/jaeger/pull/1723), [@jpkrohling](https://github.com/jpkrohling))\n* Export expvar metrics of badger to the metricsFactory ([#1704](https://github.com/jaegertracing/jaeger/pull/1704), [@burmanm](https://github.com/burmanm))\n* Pass TTL as int, not as float64 ([#1710](https://github.com/jaegertracing/jaeger/pull/1710), [@yurishkuro](https://github.com/yurishkuro))\n* Use find by regex for archive index in index cleaner ([#1693](https://github.com/jaegertracing/jaeger/pull/1693), [@pavolloffay](https://github.com/pavolloffay))\n* Allow token propagation if token type is not specified ([#1685](https://github.com/jaegertracing/jaeger/pull/1685), [@rubenvp8510](https://github.com/rubenvp8510))\n* Fix duplicated spans when querying Elasticsearch ([#1677](https://github.com/jaegertracing/jaeger/pull/1677), [@pavolloffay](https://github.com/pavolloffay))\n* Fix the threshold precision issue ([#1665](https://github.com/jaegertracing/jaeger/pull/1665), [@guanw](https://github.com/guanw))\n* Badger filter duplicate results from a single indexSeek ([#1649](https://github.com/jaegertracing/jaeger/pull/1649), [@burmanm](https://github.com/burmanm))\n* Badger make default dirs work in Windows ([#1653](https://github.com/jaegertracing/jaeger/pull/1653), [@burmanm](https://github.com/burmanm))\n\n#### UI Changes\n\n* UI pinned to version 1.4.0. The changelog is available here [v1.4.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v130-june-21-2019)\n\n1.13.1 (2019-06-28)\n------------------\n\n#### Backend Changes\n\n##### Bug fixes, Minor Improvements\n\n* Change default for bearer-token-propagation to false ([#1642](https://github.com/jaegertracing/jaeger/pull/1642), [@wsoula](https://github.com/wsoula))\n\n#### UI Changes\n\n1.13.0 (2019-06-27)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n* The traces related metrics on collector now have a new tag `sampler_type` ([#1576](https://github.com/jaegertracing/jaeger/pull/1576), [@guanw](https://github.com/guanw))\n\n  This might break some existing metrics dashboard (if so, users need to update query to aggregate over this new tag).\n\n  The list of metrics affected: `traces.received`, `traces.rejected`, `traces.saved-by-svc`.\n\n* Remove deprecated index prefix separator `:` from Elastic ([#1620](https://github.com/jaegertracing/jaeger/pull/1620), [@pavolloffay](https://github.com/pavolloffay))\n\n  In Jaeger 1.9.0 release the Elasticsearch index separator was changed from `:` to `-`. To keep backwards\n  compatibility the query service kept querying indices with `:` separator, however the new indices\n  were created only with `-`. This release of Jaeger removes the query capability for indices containing `:`,\n  therefore it's recommended to keep using older version until indices containing old separator are\n  not queried anymore.\n\n##### New Features\n\n* Passthrough OAuth bearer token supplied to Query service through to ES storage ([#1599](https://github.com/jaegertracing/jaeger/pull/1599), [@rubenvp8510](https://github.com/rubenvp8510))\n* Kafka kerberos authentication support for collector/ingester ([#1589](https://github.com/jaegertracing/jaeger/pull/1589), [@rubenvp8510](https://github.com/rubenvp8510))\n* Allow Cassandra schema builder to use credentials ([#1635](https://github.com/jaegertracing/jaeger/pull/1635), [@PS-EGHornbostel](https://github.com/PS-EGHornbostel))\n* Add docs generation command ([#1572](https://github.com/jaegertracing/jaeger/pull/1572), [@pavolloffay](https://github.com/pavolloffay))\n\n##### Bug fixes, Minor Improvements\n\n* Fix data race between `Agent.Run()` and `Agent.Stop()` ([#1625](https://github.com/jaegertracing/jaeger/pull/1625), [@tigrannajaryan](https://github.com/tigrannajaryan))\n* Use json number when unmarshalling data from ES ([#1618](https://github.com/jaegertracing/jaeger/pull/1618), [@pavolloffay](https://github.com/pavolloffay))\n* Define logs as nested data type ([#1622](https://github.com/jaegertracing/jaeger/pull/1622), [@pavolloffay](https://github.com/pavolloffay))\n* Fix archive storage not querying old spans older than maxSpanAge ([#1617](https://github.com/jaegertracing/jaeger/pull/1617), [@pavolloffay](https://github.com/pavolloffay))\n* Query service: fix logging errors on SIGINT ([#1601](https://github.com/jaegertracing/jaeger/pull/1601), [@jan25](https://github.com/jan25))\n* Direct grpc logs to Zap logger ([#1606](https://github.com/jaegertracing/jaeger/pull/1606), [@yurishkuro](https://github.com/yurishkuro))\n* Fix sending status to health check channel in Query service ([#1598](https://github.com/jaegertracing/jaeger/pull/1598), [@jan25](https://github.com/jan25))\n* Add tmp-volume to all-in-one image to fix badger storage ([#1571](https://github.com/jaegertracing/jaeger/pull/1571), [@burmanm](https://github.com/burmanm))\n* Do not fail es-cleaner if there are no jaeger indices ([#1569](https://github.com/jaegertracing/jaeger/pull/1569), [@pavolloffay](https://github.com/pavolloffay))\n* Automatically set `GOMAXPROCS` ([#1560](https://github.com/jaegertracing/jaeger/pull/1560), [@rubenvp8510](https://github.com/rubenvp8510))\n* Add CA certs to all-in-one image ([#1554](https://github.com/jaegertracing/jaeger/pull/1554), [@chandresh-pancholi](https://github.com/chandresh-pancholi))\n\n#### UI Changes\n\n* UI pinned to version 1.3.0. The changelog is available here [v1.3.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v130-june-21-2019)\n\n1.12.0 (2019-05-16)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n- The `kafka` flags were removed in favor of `kafka.producer` and `kafka.consumer` flags ([#1424](https://github.com/jaegertracing/jaeger/pull/1424), [@ledor473](https://github.com/ledor473))\n\n    The following flags have been **removed** in the Collector and the Ingester:\n    ```\n    --kafka.brokers\n    --kafka.encoding\n    --kafka.topic\n    --ingester.brokers\n    --ingester.encoding\n    --ingester.topic\n    --ingester.group-id\n    ```\n\n    In the Collector, they are replaced by:\n    ```\n    --kafka.producer.brokers\n    --kafka.producer.encoding\n    --kafka.producer.topic\n    ```\n\n    In the Ingester, they are replaced by:\n    ```\n    --kafka.consumer.brokers\n    --kafka.consumer.encoding\n    --kafka.consumer.topic\n    --kafka.consumer.group-id\n    ```\n\n* Add Admin port and group all ports in one file ([#1442](https://github.com/jaegertracing/jaeger/pull/1442), [@yurishkuro](https://github.com/yurishkuro))\n\n    This change fixes issues [#1428](https://github.com/jaegertracing/jaeger/issues/1428), [#1332](https://github.com/jaegertracing/jaeger/issues/1332) and moves all metrics endpoints from API ports to **admin ports**. It requires re-configuring Prometheus scraping rules. Each Jaeger binary has its own admin port that can be found under `--admin-http-port` command line flag by running the `${binary} help` command.\n\n##### New Features\n\n* Add gRPC resolver using external discovery service ([#1498](https://github.com/jaegertracing/jaeger/pull/1498), [@guanw](https://github.com/guanw))\n* gRPC storage plugin framework ([#1461](https://github.com/jaegertracing/jaeger/pull/1461), [@chvck](https://github.com/chvck))\n* Supports customized kafka client id ([#1507](https://github.com/jaegertracing/jaeger/pull/1507), [@newly12](https://github.com/newly12))\n* Support gRPC for query service ([#1307](https://github.com/jaegertracing/jaeger/pull/1307), [@annanay25](https://github.com/annanay25))\n* Expose tls.InsecureSkipVerify to es.tls.* CLI flags ([#1473](https://github.com/jaegertracing/jaeger/pull/1473), [@stefanvassilev](https://github.com/stefanvassilev))\n* Return info msg for `/health` endpoint ([#1465](https://github.com/jaegertracing/jaeger/pull/1465), [@stefanvassilev](https://github.com/stefanvassilev))\n* Add pprof endpoint to admin endpoint ([#1375](https://github.com/jaegertracing/jaeger/pull/1375), [@konradgaluszka](https://github.com/konradgaluszka))\n* Add inbound transport as label to collector metrics [#1446](https://github.com/jaegertracing/jaeger/pull/1446) ([guanw](https://github.com/guanw))\n* Sorted key/value store `badger` backed storage plugin ([#760](https://github.com/jaegertracing/jaeger/pull/760), [@burmanm](https://github.com/burmanm))\n* Add Admin port and group all ports in one file ([#1442](https://github.com/jaegertracing/jaeger/pull/1442), [@yurishkuro](https://github.com/yurishkuro))\n* Adds support for agent level tag ([#1396](https://github.com/jaegertracing/jaeger/pull/1396), [@annanay25](https://github.com/annanay25))\n* Add a Downsampling writer that drop a percentage of spans ([#1353](https://github.com/jaegertracing/jaeger/pull/1353), [@guanw](https://github.com/guanw))\n\n##### Bug fixes, Minor Improvements\n\n* Sort traces in memory store to return most recent traces ([#1394](https://github.com/jaegertracing/jaeger/pull/1394), [@jacobmarble](https://github.com/jacobmarble))\n* Add span format tag for jaeger-collector ([#1493](https://github.com/jaegertracing/jaeger/pull/1493), [@guo0693](https://github.com/guo0693))\n* Upgrade gRPC to 1.20.1 ([#1492](https://github.com/jaegertracing/jaeger/pull/1492), [@guanw](https://github.com/guanw))\n* Switch from counter to a gauge for partitions held ([#1485](https://github.com/jaegertracing/jaeger/pull/1485), [@bobrik](https://github.com/bobrik))\n* Add CORS handling for Zipkin collector service ([#1463](https://github.com/jaegertracing/jaeger/pull/1463), [@JonasVerhofste](https://github.com/JonasVerhofste))\n* Check elasticsearch nil response ([#1467](https://github.com/jaegertracing/jaeger/pull/1467), [@YEXINGZHE54](https://github.com/YEXINGZHE54))\n* Disable sampling in logger - `zap`([#1460](https://github.com/jaegertracing/jaeger/pull/1460), [@psinghal20](https://github.com/psinghal20))\n* New layout for proto definitions and generated files ([#1427](https://github.com/jaegertracing/jaeger/pull/1427), [@annanay25](https://github.com/annanay25))\n* Upgrade Go to 1.12.1 ([#1437](https://github.com/jaegertracing/jaeger/pull/1437) ,[@yurishkuro](https://github.com/yurishkuro))\n\n#### UI Changes\n\n* UI pinned to version 1.2.0. The changelog is available here [v1.2.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v120-may-14-2019)\n\n1.11.0 (2019-03-07)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n- Introduce `kafka.producer` and `kafka.consumer` flags to replace `kafka` flags ([#1360](https://github.com/jaegertracing/jaeger/pull/1360), [@ledor473](https://github.com/ledor473))\n\n    The following flags have been deprecated in the Collector and the Ingester:\n    ```\n    --kafka.brokers\n    --kafka.encoding\n    --kafka.topic\n    ```\n\n    In the Collector, they are replaced by:\n    ```\n    --kafka.producer.brokers\n    --kafka.producer.encoding\n    --kafka.producer.topic\n    ```\n\n    In the Ingester, they are replaced by:\n    ```\n    --kafka.consumer.brokers\n    --kafka.consumer.encoding\n    --kafka.consumer.group-id\n    ```\n##### New Features\n\n- Support secure gRPC channel between agent and collector ([#1391](https://github.com/jaegertracing/jaeger/pull/1391), [@ghouscht](https://github.com/ghouscht), [@yurishkuro](https://github.com/yurishkuro))\n- Allow to use TLS with ES basic auth ([#1388](https://github.com/jaegertracing/jaeger/pull/1388), [@pavolloffay](https://github.com/pavolloffay))\n\n##### Bug fixes, Minor Improvements\n\n- Make `esRollover.py init` idempotent ([#1407](https://github.com/jaegertracing/jaeger/pull/1407) and [#1408](https://github.com/jaegertracing/jaeger/pull/1408), [@pavolloffay](https://github.com/pavolloffay))\n- Allow thrift reporter if grpc hosts are not provided ([#1400](https://github.com/jaegertracing/jaeger/pull/1400), [@pavolloffay](https://github.com/pavolloffay))\n- Deprecate colon in index prefix in ES dependency store ([#1386](https://github.com/jaegertracing/jaeger/pull/1386), [@pavolloffay](https://github.com/pavolloffay))\n- Make grpc reporter default and add retry ([#1384](https://github.com/jaegertracing/jaeger/pull/1384), [@pavolloffay](https://github.com/pavolloffay))\n- Use `CQLSH_HOST` in final call to `cqlsh` ([#1372](https://github.com/jaegertracing/jaeger/pull/1372), [@funny-falcon](https://github.com/funny-falcon))\n\n#### UI Changes\n\n* UI pinned to version 1.1.0. The changelog is available here [v1.1.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v110-march-3-2019)\n\n\n1.10.1 (2019-02-21)\n------------------\n\n#### Backend Changes\n\n- Discover dependencies table version automatically ([#1364](https://github.com/jaegertracing/jaeger/pull/1364), [@black-adder](https://github.com/black-adder))\n\n##### Bug fixes, Minor Improvements\n\n- Separate query-service functionality from http handler ([#1312](https://github.com/jaegertracing/jaeger/pull/1312), [@annanay25](https://github.com/annanay25))\n\n#### UI Changes\n\n\n1.10.0 (2019-02-15)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n- Remove cassandra SASI indices ([#1328](https://github.com/jaegertracing/jaeger/pull/1328), [@black-adder](https://github.com/black-adder))\n\nMigration Path:\n\n1. Run `plugin/storage/cassandra/schema/migration/v001tov002part1.sh` which will copy dependencies into a csv, update the `dependency UDT`, create a new `dependencies_v2` table, and write dependencies from the csv into the `dependencies_v2` table.\n2. Run the collector and query services with the cassandra flag `cassandra.enable-dependencies-v2=true` which will instruct jaeger to write and read to and from the new `dependencies_v2` table.\n3. Update [spark job](https://github.com/jaegertracing/spark-dependencies) to write to the new `dependencies_v2` table. The feature will be done in [#58](https://github.com/jaegertracing/spark-dependencies/issues/58).\n4. Run `plugin/storage/cassandra/schema/migration/v001tov002part2.sh` which will DELETE the old dependency table and the SASI index.\n\nUsers who wish to continue to use the v1 table don't have to do anything as the cassandra flag `cassandra.enable-dependencies-v2` will default to false. Users may migrate on their own timeline however new features will be built solely on the `dependencies_v2` table. In the future, we will remove support for v1 completely.\n\n- Remove `ErrorBusy`, it essentially duplicates `SpansDropped` ([#1091](https://github.com/jaegertracing/jaeger/pull/1091), [@cstyan](https://github.com/cstyan))\n\n##### New Features\n\n- Support certificates in elasticsearch scripts ([#1339](https://github.com/jaegertracing/jaeger/pull/1399), [@pavolloffay](https://github.com/pavolloffay))\n- Add ES Rollover support to main indices ([#1309](https://github.com/jaegertracing/jaeger/pull/1309), [@pavolloffay](https://github.com/pavolloffay))\n- Load ES auth token from file ([#1319](https://github.com/jaegertracing/jaeger/pull/1319), [@pavolloffay](https://github.com/pavolloffay))\n- Add username/password authentication to ES index cleaner ([#1318](https://github.com/jaegertracing/jaeger/pull/1318), [@gregoryfranklin](https://github.com/gregoryfranklin))\n- Add implementation of FindTraceIDs function for Elasticsearch reader ([#1280](https://github.com/jaegertracing/jaeger/pull/1280), [@vlamug](https://github.com/vlamug))\n- Support archive traces for ES storage ([#1197](https://github.com/jaegertracing/jaeger/pull/1197), [@pavolloffay](https://github.com/pavolloffay))\n\n##### Bug fixes, Minor Improvements\n\n- Use Zipkin annotations if the timestamp is zero ([#1341](https://github.com/jaegertracing/jaeger/pull/1341), [@geobeau](https://github.com/geobeau))\n- Use GRPC round robin balancing even if only one hostname ([#1329](https://github.com/jaegertracing/jaeger/pull/1329), [@benley](https://github.com/benley))\n- Tolerate whitespaces in ES servers and kafka brokers ([#1305](https://github.com/jaegertracing/jaeger/pull/1305), [@verma-varsha](https://github.com/verma-varsha))\n- Let cassandra servers contain whitespace in config ([#1301](https://github.com/jaegertracing/jaeger/pull/1301), [@karlpokus](https://github.com/karlpokus))\n\n#### UI Changes\n\n\n1.9.0 (2019-01-21)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n- Change Elasticsearch index prefix from `:` to `-` ([#1284](https://github.com/jaegertracing/jaeger/pull/1284), [@pavolloffay](https://github.com/pavolloffay))\n\nChanged index prefix separator from `:`  to `-` because Elasticsearch 7 does not allow `:` in index name.\nJaeger query still reads from old indices containing `-` as a separator, therefore no configuration or migration changes are required.\n\n\n\n- Add CLI configurable `es.max-num-spans` while retrieving spans from ES ([#1283](https://github.com/jaegertracing/jaeger/pull/1283), [@annanay25](https://github.com/annanay25))\n\nThe default value is set to 10000. Before no limit was applied.\n\n\n- Update to jaeger-lib 2 and latest sha for jaeger-client-go, to pick up refactored metric names ([#1282](https://github.com/jaegertracing/jaeger/pull/1282), [@objectiser](https://github.com/objectiser))\n\nUpdate to latest version of `jaeger-lib`, which includes a change to the naming of counters exported to\nprometheus, to follow the convention of using a `_total` suffix, e.g. `jaeger_query_requests` is now\n`jaeger_query_requests_total`.\n\nJaeger go client metrics, previously under the namespace `jaeger_client_jaeger_` are now under\n`jaeger_tracer_`.\n\n\n- Add gRPC metrics to agent ([#1180](https://github.com/jaegertracing/jaeger/pull/1180), [@pavolloffay](https://github.com/pavolloffay))\n\nThe following metrics:\n```\njaeger_agent_tchannel_reporter_batch_size{format=\"jaeger\"} 0\njaeger_agent_tchannel_reporter_batch_size{format=\"zipkin\"} 0\njaeger_agent_tchannel_reporter_batches_failures{format=\"jaeger\"} 0\njaeger_agent_tchannel_reporter_batches_failures{format=\"zipkin\"} 0\njaeger_agent_tchannel_reporter_batches_submitted{format=\"jaeger\"} 0\njaeger_agent_tchannel_reporter_batches_submitted{format=\"zipkin\"} 0\njaeger_agent_tchannel_reporter_spans_failures{format=\"jaeger\"} 0\njaeger_agent_tchannel_reporter_spans_failures{format=\"zipkin\"} 0\njaeger_agent_tchannel_reporter_spans_submitted{format=\"jaeger\"} 0\njaeger_agent_tchannel_reporter_spans_submitted{format=\"zipkin\"} 0\n\njaeger_agent_collector_proxy{endpoint=\"baggage\",result=\"err\"} 0\njaeger_agent_collector_proxy{endpoint=\"baggage\",result=\"ok\"} 0\njaeger_agent_collector_proxy{endpoint=\"sampling\",result=\"err\"} 0\njaeger_agent_collector_proxy{endpoint=\"sampling\",result=\"ok\"} 0\n```\nhave been renamed to:\n```\njaeger_agent_reporter_batch_size{format=\"jaeger\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_batch_size{format=\"zipkin\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_batches_failures{format=\"jaeger\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_batches_failures{format=\"zipkin\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_batches_submitted{format=\"jaeger\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_batches_submitted{format=\"zipkin\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_spans_failures{format=\"jaeger\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_spans_failures{format=\"zipkin\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_spans_submitted{format=\"jaeger\",protocol=\"tchannel\"} 0\njaeger_agent_reporter_spans_submitted{format=\"zipkin\",protocol=\"tchannel\"} 0\n\njaeger_agent_collector_proxy{endpoint=\"baggage\",protocol=\"tchannel\",result=\"err\"} 0\njaeger_agent_collector_proxy{endpoint=\"baggage\",protocol=\"tchannel\",result=\"ok\"} 0\njaeger_agent_collector_proxy{endpoint=\"sampling\",protocol=\"tchannel\",result=\"err\"} 0\njaeger_agent_collector_proxy{endpoint=\"sampling\",protocol=\"tchannel\",result=\"ok\"} 0\n```\n\n- Rename tcollector proxy metric in agent ([#1182](https://github.com/jaegertracing/jaeger/pull/1182), [@pavolloffay](https://github.com/pavolloffay))\n\nThe following metric:\n```\njaeger_http_server_errors{source=\"tcollector-proxy\",status=\"5xx\"}\n```\nhas been renamed to:\n```\njaeger_http_server_errors{source=\"collector-proxy\",status=\"5xx\"}\n```\n\n##### New Features\n\n- Add tracegen utility for generating traces ([#1245](https://github.com/jaegertracing/jaeger/pull/1245), [@yurishkuro](https://github.com/yurishkuro))\n- Use DCAwareRoundRobinPolicy as fallback for TokenAwarePolicy ([#1285](https://github.com/jaegertracing/jaeger/pull/1285), [@vprithvi](https://github.com/vprithvi))\n- Add Zipkin Thrift as kafka ingestion format ([#1256](https://github.com/jaegertracing/jaeger/pull/1256), [@geobeau](https://github.com/geobeau))\n- Add `FindTraceID` to the spanstore interface ([#1246](https://github.com/jaegertracing/jaeger/pull/1246), [@vprithvi](https://github.com/vprithvi))\n- Migrate from glide to dep ([#1240](https://github.com/jaegertracing/jaeger/pull/1240), [@isaachier](https://github.com/isaachier))\n- Make tchannel timeout for reporting in agent configurable ([#1034](https://github.com/jaegertracing/jaeger/pull/1034), [@gouthamve](https://github.com/gouthamve))\n- Add archive traces to all-in-one ([#1189](https://github.com/jaegertracing/jaeger/pull/1189), [@pavolloffay](https://github.com/pavolloffay))\n- Start moving components of adaptive sampling to OSS ([#973](https://github.com/jaegertracing/jaeger/pull/973), [@black-adder](https://github.com/black-adder))\n- Add gRPC communication between agent and collector ([#1165](https://github.com/jaegertracing/jaeger/pull/1165), [#1187](https://github.com/jaegertracing/jaeger/pull/1187), [#1181](https://github.com/jaegertracing/jaeger/pull/1181) and [#1180](https://github.com/jaegertracing/jaeger/pull/1180), [@pavolloffay](https://github.com/pavolloffay))\n\n##### Bug fixes, Minor Improvements\n\n- Update exposed ports in ingester dockerfile ([#1289](https://github.com/jaegertracing/jaeger/pull/1289), [@objectiser](https://github.com/objectiser))\n- Upgrade Shopify/Sarama for proper handling newest kafka servers 2.x by ingester ([#1248](https://github.com/jaegertracing/jaeger/pull/1248), [@vprithvi](https://github.com/vprithvi))\n- Fix sampling strategies overwriting service entry when no sampling type is specified ([#1244](https://github.com/jaegertracing/jaeger/pull/1244), [@objectiser](https://github.com/objectiser))\n- Fix dot replacement for int ([#1272](https://github.com/jaegertracing/jaeger/pull/1272), [@pavolloffay](https://github.com/pavolloffay))\n- Add C* query to error logs ([#1250](https://github.com/jaegertracing/jaeger/pull/1250), [@vprithvi](https://github.com/vprithvi))\n- Add locking around partitionIDToState map accesses ([#1239](https://github.com/jaegertracing/jaeger/pull/1239), [@vprithvi](https://github.com/vprithvi))\n- Reorganize config manager packages in agent ([#1198](https://github.com/jaegertracing/jaeger/pull/1198), [@pavolloffay](https://github.com/pavolloffay))\n\n#### UI Changes\n\n* UI pinned to version 1.0.0. The changelog is available here [v1.0.0](https://github.com/jaegertracing/jaeger-ui/blob/main/CHANGELOG.md#v100-january-18-2019)\n\n1.8.2 (2018-11-28)\n------------------\n\n#### UI Changes\n\n##### New Features\n\n- Embedded components (SearchTraces and Tracepage) ([#263](https://github.com/jaegertracing/jaeger/pull/263), [@aljesusg](https://github.com/aljesusg))\n\n##### Bug fixes, Minor Improvements\n\n- Fix link in scatter plot when embed mode ([#283](https://github.com/jaegertracing/jaeger-ui/pull/283), [@aljesusg](https://github.com/aljesusg))\n- Fix rendering X axis in TraceResultsScatterPlot - pass milliseconds to moment.js ([#274](https://github.com/jaegertracing/jaeger-ui/pull/274), [@istrel](https://github.com/istrel))\n\n\n1.8.1 (2018-11-23)\n------------------\n\n#### Backend Changes\n\n##### Bug fixes, Minor Improvements\n\n- Make agent timeout for reporting configurable and fix flags overriding ([#1034](https://github.com/jaegertracing/jaeger/pull/1034), [@gouthamve](https://github.com/gouthamve))\n- Fix metrics handler registration in agent ([#1178](https://github.com/jaegertracing/jaeger/pull/1178), [@pavolloffay](https://github.com/pavolloffay))\n\n\n1.8.0 (2018-11-12)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n- Refactor agent configuration ([#1092](https://github.com/jaegertracing/jaeger/pull/1092), [@pavolloffay](https://github.com/pavolloffay))\n\nThe following agent flags has been deprecated in order to support multiple reporters:\n```bash\n--collector.host-port\n--discovery.conn-check-timeout\n--discovery.min-peers\n```\nNew flags:\n```bash\n--reporter.tchannel.host-port\n--reporter.tchannel.discovery.conn-check-timeout\n--reporter.tchannel.discovery.min-peers\n```\n\n- Various changes around metrics produced by jaeger-query: Names scoped to the query component, generated for all span readers (not just ES), consolidate query metrics and include result tag ([#1074](https://github.com/jaegertracing/jaeger/pull/1074), [#1075](https://github.com/jaegertracing/jaeger/pull/1075) and [#1096](https://github.com/jaegertracing/jaeger/pull/1096), [@objectiser](https://github.com/objectiser))\n\nFor example, sample of metrics produced for `find_traces` operation before:\n\n```\njaeger_find_traces_attempts 1\njaeger_find_traces_errLatency_bucket{le=\"0.005\"} 0\njaeger_find_traces_errors 0\njaeger_find_traces_okLatency_bucket{le=\"0.005\"} 0\njaeger_find_traces_responses_bucket{le=\"0.005\"} 1\njaeger_find_traces_successes 1\n```\n\nAnd now:\n\n```\njaeger_query_latency_bucket{operation=\"find_traces\",result=\"err\",le=\"0.005\"} 0\njaeger_query_latency_bucket{operation=\"find_traces\",result=\"ok\",le=\"0.005\"} 2\njaeger_query_requests{operation=\"find_traces\",result=\"err\"} 0\njaeger_query_requests{operation=\"find_traces\",result=\"ok\"} 2\njaeger_query_responses_bucket{operation=\"find_traces\",le=\"0.005\"} 2\n```\n\n##### New Features\n\n- Configurable deadlock detector interval for ingester ([#1134](https://github.com/jaegertracing/jaeger/pull/1134), [@marqc](https://github.com/marqc))\n- Emit spans for elastic storage backend ([#1128](https://github.com/jaegertracing/jaeger/pull/1128), [@annanay25](https://github.com/annanay25))\n- Allow to use TLS certificates for Elasticsearch authentication ([#1139](https://github.com/jaegertracing/jaeger/pull/1139), [@clyang82](https://github.com/clyang82))\n- Add ingester metrics, healthcheck and rename Kafka cli flags ([#1094](https://github.com/jaegertracing/jaeger/pull/1094), [@ledor473](https://github.com/ledor473))\n- Add a metric for number of partitions held ([#1154](https://github.com/jaegertracing/jaeger/pull/1154), [@vprithvi](https://github.com/vprithvi))\n- Log jaeger-collector tchannel port ([#1136](https://github.com/jaegertracing/jaeger/pull/1136), [@mindaugasrukas](https://github.com/mindaugasrukas))\n- Support tracer env based initialization in hotrod ([#1115](https://github.com/jaegertracing/jaeger/pull/1115), [@eundoosong](https://github.com/eundoosong))\n- Publish ingester as binaries and docker image ([#1086](https://github.com/jaegertracing/jaeger/pull/1086), [@ledor473](https://github.com/ledor473))\n- Use Go 1.11 ([#1104](https://github.com/jaegertracing/jaeger/pull/1104), [@isaachier](https://github.com/isaachier))\n- Tag images with commit SHA and publish to `-snapshot` repository ([#1082](https://github.com/jaegertracing/jaeger/pull/1082), [@pavolloffay](https://github.com/pavolloffay))\n\n##### Bug fixes, Minor Improvements\n\n- Fix child span context while tracing cassandra queries ([#1131](https://github.com/jaegertracing/jaeger/pull/1131), [@annanay25](https://github.com/annanay25))\n- Deadlock detector hack for Kafka driver instability ([#1087](https://github.com/jaegertracing/jaeger/pull/1087), [@vprithvi](https://github.com/vprithvi))\n- Fix processor overriding data in a buffer ([#1099](https://github.com/jaegertracing/jaeger/pull/1099), [@pavolloffay](https://github.com/pavolloffay))\n\n#### UI Changes\n\n##### New Features\n\n- Span Search - Highlight search results ([#238](https://github.com/jaegertracing/jaeger-ui/pull/238)), [@davit-y](https://github.com/davit-y)\n- Span Search - Improve search logic ([#237](https://github.com/jaegertracing/jaeger-ui/pull/237)),  [@davit-y](https://github.com/davit-y)\n- Span Search - Add result count, navigation and clear buttons ([#234](https://github.com/jaegertracing/jaeger-ui/pull/234)), [@davit-y](https://github.com/davit-y)\n\n##### Bug Fixes, Minor Improvements\n\n- Use correct duration format for scatter plot ([#266](https://github.com/jaegertracing/jaeger-ui/pull/266)), [@tiffon](https://github.com/tiffon))\n- Fix collapse all issues ([#264](https://github.com/jaegertracing/jaeger-ui/pull/264)), [@tiffon](https://github.com/tiffon))\n- Use a moderately sized canvas for the span graph ([#257](https://github.com/jaegertracing/jaeger-ui/pull/257)), [@tiffon](https://github.com/tiffon))\n\n\n1.7.0 (2018-09-19)\n------------------\n\n#### UI Changes\n\n- Compare two traces ([#228](https://github.com/jaegertracing/jaeger-ui/pull/228), [@tiffon](https://github.com/tiffon))\n- Make tags clickable ([#223](https://github.com/jaegertracing/jaeger-ui/pull/223), [@divdavem](https://github.com/divdavem))\n- Directed graph as React component ([#224](https://github.com/jaegertracing/jaeger-ui/pull/224), [@tiffon](https://github.com/tiffon))\n- Timeline Expand and Collapse Features ([#221](https://github.com/jaegertracing/jaeger-ui/issues/221), [@davit-y](https://github.com/davit-y))\n- Integrate Google Analytics into Search Page ([#220](https://github.com/jaegertracing/jaeger-ui/issues/220), [@davit-y](https://github.com/davit-y))\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n- `jaeger-standalone` binary has been renamed to `jaeger-all-in-one`. This change also includes package rename from `standalone` to `all-in-one` ([#1062](https://github.com/jaegertracing/jaeger/pull/1062), [@pavolloffay](https://github.com/pavolloffay))\n\n##### New Features\n\n- (Experimental) Allow storing tags as object fields in Elasticsearch for better Kibana support(([#1018](https://github.com/jaegertracing/jaeger/pull/1018), [@pavolloffay](https://github.com/pavolloffay))\n- Enable tracing of Cassandra queries ([#1038](https://github.com/jaegertracing/jaeger/pull/1038), [@yurishkuro](https://github.com/yurishkuro))\n- Make Elasticsearch index configurable ([#1009](https://github.com/jaegertracing/jaeger/pull/1009), [@pavolloffay](https://github.com/pavoloffay))\n- Add flags to allow changing ports for HotROD services ([#951](https://github.com/jaegertracing/jaeger/pull/951), [@cboornaz17](https://github.com/cboornaz17))\n- (Experimental) Kafka ingester ([#952](https://github.com/jaegertracing/jaeger/pull/952), [#942](https://github.com/jaegertracing/jaeger/pull/942), [#944](https://github.com/jaegertracing/jaeger/pull/944), [#940](https://github.com/jaegertracing/jaeger/pull/940), [@davit-y](https://github.com/davit-y) and [@vprithvi](https://github.com/vprithvi)))\n- Use tags in agent metrics ([#950](https://github.com/jaegertracing/jaeger/pull/950), [@eundoosong](https://github.com/eundoosong))\n- Add support for Cassandra reconnect interval ([#934](https://github.com/jaegertracing/jaeger/pull/934), [@nyanshak](https://github.com/nyanshak))\n\n1.6.0 (2018-07-10)\n------------------\n\n#### Backend Changes\n\n##### ⛔ Breaking Changes\n\n- The storage implementations no longer write the parentSpanID field to storage (#856).\n  If you are upgrading to this version, **you must upgrade query service first**!\n\n- Update Dockerfiles to reference executable via ENTRYPOINT (#815) by Zachary DiCesare (@zdicesare)\n\n  It is no longer necessary to specify the binary name when passing flags to containers.\n  For example, to execute the `help` command of the collector, instead of\n  ```\n  $ docker run -it --rm jaegertracing/jaeger-collector /go/bin/collector-linux help\n  ```\n  run\n  ```\n  $ docker run -it --rm jaegertracing/jaeger-collector help\n  ```\n\n- Detect HTTP payload format from Content-Type (#916) by Yuri Shkuro (@yurishkuro)\n\n  When submitting spans in Thrift format to HTTP endpoint `/api/traces`,\n  the `format` argument is no longer required, but the Content-Type header\n  must be set to \"application/vnd.apache.thrift.binary\".\n\n- Change metric tag from \"service\" to \"svc\" (#883) by Won Jun Jang (@black-adder)\n\n##### New Features\n\n- Add Kafka as a Storage Plugin (#862) by David Yeghshatyan (@davit-y)\n\n  The collectors can be configured to write spans to Kafka for further data mining.\n\n- Package static assets inside the query-service binary (#918) by Yuri Shkuro (@yurishkuro)\n\n  It is no longer necessary (but still possible) to pass the path to UI static assets\n  to jaeger-query and jaeger-standalone binaries.\n\n- Replace domain model with Protobuf/gogo-generated model (#856) by Yuri Shkuro (@yurishkuro)\n\n  First step towards switching to Protobuf and gRPC.\n\n- Include HotROD binary in the distributions (#917) by Yuri Shkuro (@yurishkuro)\n- Improve HotROD demo (#915) by Yuri Shkuro (@yurishkuro)\n- Add DisableAutoDiscovery param to cassandra config (#912) by Bill Westlin (@whistlinwilly)\n- Add connCheckTimeout flag to agent (#911) by Henrique Rodrigues (@Henrod)\n- Ability to use multiple storage types (#880) by David Yeghshatyan (@davit-y)\n\n##### Minor Improvements\n\n- [ES storage] Log number of total and failed requests (#902) by Tomasz Adamski (@tmszdmsk)\n- [ES storage] Do not log requests on error (#901) by Tomasz Adamski (@tmszdmsk)\n- [ES storage] Do not exceed ES _id length limit (#905) by Łukasz Harasimowicz (@harnash) and Tomasz Adamski (@tmszdmsk)\n- Add cassandra index filter (#876) by Won Jun Jang (@black-adder)\n- Close span writer in standalone (#863) (4 weeks ago) by Pavol Loffay (@pavolloffay)\n- Log configuration options for memory storage (#852) (6 weeks ago) by Juraci Paixão Kröhling (@jpkrohling)\n- Update collector metric counters to have a name (#886) by Won Jun Jang (@black-adder)\n- Add CONTRIBUTING_GUIDELINES.md (#864) by (@PikBot)\n\n1.5.0 (2018-05-28)\n------------------\n\n#### Backend Changes\n\n- Add bounds to memory storage (#845) by Juraci Paixão Kröhling (@jpkrohling)\n- Add metric for debug traces (#796) by Won Jun Jang (@black-adder)\n- Change metrics naming scheme (#776) by Juraci Paixão Kröhling (@jpkrohling)\n- Remove ParentSpanID from domain model (#831) by Yuri Shkuro (@yurishkuro)\n- Add ability to adjust static sampling probabilities per operation (#827) by Won Jun Jang (@black-adder)\n- Support log-level flag on agent (#828) by Won Jun Jang (@black-adder)\n- Add healthcheck to standalone (#784) by Eundoo Song (@eundoosong)\n- Do not use KeyValue fields directly and use KeyValues as decorator only (#810) by Yuri Shkuro (@yurishkuro)\n- Upgrade to go 1.10 (#792) by Prithvi Raj (@vprithvi)\n- Do not create Cassandra index if it already exists (#782) by Greg Swift (@gregswift)\n\n#### UI Changes\n\n- None\n\n1.4.1 (2018-04-21)\n------------------\n\n#### Backend Changes\n\n- Publish binaries for Linux, Darwin, and Windows (#765) - thanks to @grounded042\n\n#### UI Changes\n\n##### New Features\n\n- View Trace JSON buttons return formatted JSON (fixes [#199](https://github.com/jaegertracing/jaeger-ui/issues/199))\n\n\n1.4.0 (2018-04-20)\n------------------\n\n#### Backend Changes\n\n##### New Features\n\n- Support traces with >10k spans in Elasticsearch (#668) - thanks to @sramakr\n\n##### Bug Fixes, Minor Improvements\n\n- Allow slash '/' in service names (#586)\n- Log errors from HotROD services (#769)\n\n\n1.3.0 (2018-03-26)\n------------------\n\n#### Backend Changes\n\n##### New Features\n\n- Add sampling handler with file-based configuration for agents to query (#720) (#674) <Won Jun Jang>\n- Allow overriding base path for UI/API routes and remove --query.prefix (#748) <Yuri Shkuro>\n- Add Dockerfile for hotrod example app (#694) <Guilherme Baufaker Rêgo>\n- Publish hotrod image to docker hub (#702) <Pavol Loffay>\n- Dockerize es-index-cleaner script (#741) <Pavol Loffay>\n- Add a flag to control Cassandra consistency level (#700) <Yuri Shkuro>\n- Collect metrics from ES bulk service (#688) <Pavol Loffay>\n- Allow zero replicas for Elasticsearch (#754) <bharat-p>\n\n##### Bug Fixes, Minor Improvements\n\n- Apply namespace when creating Prometheus metrics factory (fix for #732) (#733) <Yuri Shkuro>\n- Disable double compression on Prom Handler - fixes #697 (#735) <Juraci Paixão Kröhling>\n- Use the default metricsFactory if not provided (#739) <Louis-Etienne>\n- Avoid duplicate expvar metrics - fixes #716 (#726) <Yuri Shkuro>\n- Make sure different tracers in HotROD process use different random generator seeds (#718) <Yuri Shkuro>\n- Test that processes with identical tags are deduped (#708) <Yuri Shkuro>\n- When converting microseconds to time.Time ensure UTC timezone (#712) <Prithvi Raj>\n- Add to WaitGroup before the goroutine creation (#711) <Cruth kvinc>\n- Pin testify version to ^1.2.1 (#710) <Pavol Loffay>\n\n#### UI Changes\n\n##### New Features\n\n- Support running Jaeger behind a reverse proxy (fixes [#42](https://github.com/jaegertracing/jaeger-ui/issues/42))\n- Track Javascript errors via Google Analytics (fixes [#39](https://github.com/jaegertracing/jaeger-ui/issues/39))\n- Add Google Analytics event tracking for actions in trace view ([#191](https://github.com/jaegertracing/jaeger-ui/issues/191))\n\n##### Bug Fixes, Minor Improvements\n\n- Clearly identify traces without a root span (fixes [#190](https://github.com/jaegertracing/jaeger-ui/issues/190))\n- Fix [#166](https://github.com/jaegertracing/jaeger-ui/issues/166) JS error on search page after viewing 404 trace\n\n#### Documentation Changes\n\n\n1.2.0 (2018-02-07)\n------------------\n\n#### Backend Changes\n\n##### New Features\n\n- Use elasticsearch bulk API (#656) <Pavol Loffay>\n- Support archive storage in the query-service (#604) <Yuri Shkuro>\n- Introduce storage factory framework and composable CLI (#625) <Yuri Shkuro>\n- Make agent host port configurable in hotrod (#663) <Pavol Loffay>\n- Add signal handling to standalone (#657) <Pavol Loffay>\n\n##### Bug Fixes, Minor Improvements\n\n- Remove the override of GOMAXPROCS (#679) <Cruth kvinc>\n- Use UTC timezone for ES indices (#646) <Pavol Loffay>\n- Fix elasticsearch create index race condition error (#641) <Pavol Loffay>\n\n#### UI Changes\n\n##### New Features\n\n- Use Ant Design instead of Semantic UI (https://github.com/jaegertracing/jaeger-ui/pull/169)\n  - Fix [#164](https://github.com/jaegertracing/jaeger-ui/issues/164) - Use Ant Design instead of Semantic UI\n  - Fix [#165](https://github.com/jaegertracing/jaeger-ui/issues/165) - Search results are shown without a date\n  - Fix [#69](https://github.com/jaegertracing/jaeger-ui/issues/69) - Missing endpoints in jaeger ui dropdown\n\n##### Bug Fixes, Minor Improvements\n\n- Fix 2 digit lookback (12h, 24h) parsing (https://github.com/jaegertracing/jaeger-ui/issues/167)\n\n\n1.1.0 (2018-01-03)\n------------------\n\n#### Backend Changes\n\n##### New Features\n\n- Add support for retrieving unadjusted/raw traces (#615)\n- Add CA certificates to collector/query images (#485)\n- Parse zipkin v2 high trace id (#596)\n\n##### Bug Fixes, Minor Improvements\n\n- Skip nil and zero length hits in ElasticSearch storage (#601)\n- Make Cassandra service_name_index inserts idempotent (#587)\n- Align atomic int64 to word boundary to fix SIGSEGV (#592)\n- Add adjuster that removes bad span references (#614)\n- Set operationNames cache initial capacity to 10000 (#621)\n\n#### UI Changes\n\n##### New Features\n\n- Change tag search input syntax to logfmt (https://github.com/jaegertracing/jaeger-ui/issues/145)\n- Make threshold for enabling DAG view configurable (https://github.com/jaegertracing/jaeger-ui/issues/130)\n- Show better error messages for failed API calls (https://github.com/jaegertracing/jaeger-ui/issues/127)\n- Add View Option for raw/unadjusted trace (https://github.com/jaegertracing/jaeger-ui/issues/153)\n- Add timezone tooltip to custom lookback form-field (https://github.com/jaegertracing/jaeger-ui/pull/161)\n\n##### Bug Fixes, Minor Improvements\n\n- Use consistent icons for logs expanded/collapsed (https://github.com/jaegertracing/jaeger-ui/issues/86)\n- Encode service name in API calls to allow '/' (https://github.com/jaegertracing/jaeger-ui/issues/138)\n- Fix endless trace HTTP requests (https://github.com/jaegertracing/jaeger-ui/issues/128)\n- Fix JSON view when running in dev mode (https://github.com/jaegertracing/jaeger-ui/issues/139)\n- Fix trace name resolution (https://github.com/jaegertracing/jaeger-ui/pull/134)\n- Only JSON.parse JSON strings in tags/logs values (https://github.com/jaegertracing/jaeger-ui/pull/162)\n\n\n1.0.0 (2017-12-04)\n------------------\n\n#### Backend Changes\n\n- Support Prometheus metrics as default for all components (#516)\n- Enable TLS client connections to Cassandra (#555)\n- Fix issue where Domain to UI model converter double reports references (#579)\n\n#### UI Changes\n\n- Make dependencies tab configurable (#122)\n\n\n0.10.0 (2017-11-17)\n------------------\n\n#### UI Changes\n\n- Verify stored search settings [jaegertracing/jaeger-ui#111](https://github.com/jaegertracing/jaeger-ui/pull/111)\n- Fix browser back button not working correctly [jaegertracing/jaeger-ui#110](https://github.com/jaegertracing/jaeger-ui/pull/110)\n- Handle FOLLOWS_FROM ref type [jaegertracing/jaeger-ui#118](https://github.com/jaegertracing/jaeger-ui/pull/118)\n\n#### Backend Changes\n\n- Allow embedding custom UI config in index.html [#490](https://github.com/jaegertracing/jaeger/pull/490)\n- Add startTimeMillis field to JSON Spans submitted to ElasticSearch [#491](https://github.com/jaegertracing/jaeger/pull/491)\n- Introduce version command and handler [#517](https://github.com/jaegertracing/jaeger/pull/517)\n- Fix ElasticSearch aggregation errors when index is empty [#535](https://github.com/jaegertracing/jaeger/pull/535)\n- Change package from uber to jaegertracing [#528](https://github.com/jaegertracing/jaeger/pull/528)\n- Introduce logging level configuration [#514](https://github.com/jaegertracing/jaeger/pull/514)\n- Support Zipkin v2 json [#518](https://github.com/jaegertracing/jaeger/pull/518)\n- Add HTTP compression handler [#545](https://github.com/jaegertracing/jaeger/pull/545)\n\n\n0.9.0 (2017-10-25)\n------------------\n\n#### UI Changes\n\n- Refactor trace detail [jaegertracing/jaeger-ui#53](https://github.com/jaegertracing/jaeger-ui/pull/53)\n- Virtualized scrolling for trace detail view [jaegertracing/jaeger-ui#68](https://github.com/jaegertracing/jaeger-ui/pull/68)\n- Mouseover expands truncated text to full length in left column in trace view [jaegertracing/jaeger-ui#71](https://github.com/jaegertracing/jaeger-ui/pull/71)\n- Make left column adjustable in trace detail view [jaegertracing/jaeger-ui#74](https://github.com/jaegertracing/jaeger-ui/pull/74)\n- Fix trace mini-map blurriness when < 60 spans [jaegertracing/jaeger-ui#77](https://github.com/jaegertracing/jaeger-ui/pull/77)\n- Fix Google Analytics tracking [jaegertracing/jaeger-ui#81](https://github.com/jaegertracing/jaeger-ui/pull/81)\n- Improve search dropdowns [jaegertracing/jaeger-ui#84](https://github.com/jaegertracing/jaeger-ui/pull/84)\n- Add keyboard shortcuts and minimap UX [jaegertracing/jaeger-ui#93](https://github.com/jaegertracing/jaeger-ui/pull/93)\n\n#### Backend Changes\n\n- Add tracing to the query server [#454](https://github.com/uber/jaeger/pull/454)\n- Support configuration files [#462](https://github.com/uber/jaeger/pull/462)\n- Add cassandra tag filter [#442](https://github.com/uber/jaeger/pull/442)\n- Handle ports > 32k in Zipkin JSON [#488](https://github.com/uber/jaeger/pull/488)\n\n\n0.8.0 (2017-09-24)\n------------------\n\n- Convert to Apache 2.0 License\n\n\n0.7.0 (2017-08-22)\n------------------\n\n- Add health check server to collector and query [#280](https://github.com/uber/jaeger/pull/280)\n- Add/fix sanitizer for Zipkin span start time and duration [#333](https://github.com/uber/jaeger/pull/333)\n- Support Zipkin json encoding for /api/v1/spans HTTP endpoint [#348](https://github.com/uber/jaeger/pull/348)\n- Support Zipkin 128bit traceId and ipv6 [#349](https://github.com/uber/jaeger/pull/349)\n\n\n0.6.0 (2017-08-09)\n------------------\n\n- Add viper/cobra configuration support [#245](https://github.com/uber/jaeger/pull/245) [#307](https://github.com/uber/jaeger/pull/307)\n- Add Zipkin /api/v1/spans endpoint [#282](https://github.com/uber/jaeger/pull/282)\n- Add basic authenticator to configs for cass\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Community Code of Conduct\n\nJaeger follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).\n\nPlease contact the [Jaeger Maintainers](mailto:cncf-jaeger-maintainers@lists.cncf.io) or the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to Contribute to Jaeger\n\nWe'd love your help!\n\nGeneral contributing guidelines are described in [Contributing Guidelines](./CONTRIBUTING_GUIDELINES.md).\n\nJaeger is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub\npull requests. This document outlines some of the conventions on development\nworkflow, commit message formatting, contact points and other resources to make\nit easier to get your contribution accepted.\n\nWe gratefully welcome improvements to documentation as well as to code.\n\n## Getting Started\n\n### Pre-requisites\n* Install [Go](https://golang.org/doc/install) and setup GOPATH and add $GOPATH/bin in PATH\n\nThis library uses Go modules to manage dependencies.\n\nIf you are running `make test` or other Makefile targets on macOS, please ensure that you have GNU `sed` installed.\n\nTo install GNU `sed`:\n\n```bash\nbrew install gnu-sed\n```\n\n```\ngit clone git@github.com:jaegertracing/jaeger.git jaeger\ncd jaeger\n```\n\nThen install dependencies and run the tests:\n\n```\n# Adds the jaeger-ui submodule\ngit submodule update --init --recursive\n\n# Installs required tools\nmake install-tools\n\n# Runs all unit tests:\nmake test\n```\n\n### Contributing Code\n\nWe accept new changes as pull requests on GitHub. Please make sure the following conditions are met before submitting PRs:\n\n1. Use a named branch in your fork, not the `main` branch, otherwise the CI jobs will fail and we won't be able to merge the PR.\n2. All commits in the PR must be signed (verified by the DCO check on GitHub).\n3. Before submitting a PR, make sure to run:\n```\nmake fmt  # commit all changes from auto-format\nmake lint\nmake test\n```\n\n\n### Auto-format\n\nWe are currently using `gofumpt`, which is installed automatically by `make install-tools` as part of `golangci-lint` installation. We recommend configuring your IDE to run `gofumpt` on file saves, e.g. in VSCode:\n\n```json\n\"go.formatTool\": \"gofumpt\",\n\"gopls\": {\n    \"formatting.gofumpt\": true,\n}\n```\n\n### Running local build with the UI\n\n```\n$ go run ./cmd/jaeger --config ./cmd/jaeger/config.yaml\n```\n\n#### What does this command do?\n\nThe Jaeger binary runs with the default configuration file (config.yaml) that includes \nthe UI configuration via the `jaeger_query` extension. The `jaeger-ui` submodule, which was added from the Pre-requisites step above, contains the source code for the UI assets (requires Node.js 24+). The assets must be compiled first with `make build-ui`, which normally downloads them from the latest UI release, but can also build them from source.\n\n## Project Structure\n\nThese are general guidelines on how to organize source code in this repository.\n\n```\ngithub.com/jaegertracing/jaeger\n  cmd/                      - All binaries go here\n    jaeger/                 - The main Jaeger binary (v2) that combines collector, query, and ingester\n    anonymizer/             - Utility to anonymize traces from Jaeger query and save to file\n    tracegen/               - Utility to generate a steady flow of simple traces\n    es-index-cleaner/       - Utility to purge old indices from Elasticsearch\n    es-rollover/            - Utility to manage Elastic Search indices\n    esmapping-generator/    - Utility to generate Elasticsearch mapping\n    remote-storage/         - Component to enable sharing single-node storage implementations via Remote Storage API v2\n  examples/\n    grafana-integration/    - Demo application combining Jaeger, Grafana, Loki, Prometheus\n    hotrod/                 - Demo application demonstrating tracing instrumentation\n    otel-demo/              - Demo application using OpenTelemetry Collector and Jaeger\n  docker-compose/           - Docker-compose recipes to simulate different Jaeger deployments\n    monitor/                - Service Performance Monitoring (SPM) Development/Demo Environment\n  idl/                      - (submodule) https://github.com/jaegertracing/jaeger-idl\n  jaeger-ui/                - (submodule) https://github.com/jaegertracing/jaeger-ui\n  internal/                 - Internal modules that make up Jaeger\n    storage/                - Trace/Metrics Storage interfaces and implementations\n      metricstore/          - Metrics Storage interface and implementations (e.g. Prometheus, Elasticsearch)\n      v1/                   - Trace Storage v1 interfaces and implementations (Cassandra, Elasticsearch, Badger, etc.)\n      v2/                   - Trace Storage v2 interfaces and implementations (gRPC, ClickHouse, etc.)\n  monitoring/               - Jaeger monitoring assets (e.g. jaeger-mixin)\n  ports/                    - Centralized port definitions\n  scripts/                  - Miscellaneous project scripts, e.g. github action and license update script\n  go.mod                    - Go module file to track dependencies\n  Makefile                  - Define various recipes to automate build, test, and deployment tasks\n```\n\n## Imports grouping\n\nThis project follows the following pattern for grouping imports in Go files:\n\n- imports from standard library\n- imports from other projects\n- imports from `jaeger` project\n\nFor example:\n\n```go\nimport (\n\t\"fmt\"\n\n\t\"github.com/uber/jaeger-lib/metrics\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/agent/app\"\n\t\"github.com/jaegertracing/jaeger/cmd/collector/app/builder\"\n)\n```\n\n## Testing guidelines\n\n**Policy**: All new functionality must include tests. Bug fixes should include regression tests that would have caught the bug, where feasible. Pull requests without adequate test coverage will not be merged.\n\nWe strive to maintain as high code coverage as possible. The current repository limit is set at 95%,\nwith some exclusions discussed below.\n\n### Packages with no tests\n\nSince `go test` command does not generate\ncode coverage information for packages that have no test files, we have a build step (`make nocover`)\nthat breaks the build when such packages are discovered, with the following error:\n\n```\nerror: at least one *_test.go file must be in all directories with go files\n       so that they are counted for code coverage.\n       If no tests are possible for a package (e.g. it only defines types), create empty_test.go\n```\n\nAs the message says, all packages are required to have at least one `*_test.go` file.\n\n### Excluding packages from testing\n\nThere are conditions that cannot be tested without external dependencies, such as a function that\ncreates a `gocql.Session`, because it requires an active connection to Cassandra database. It is\nrecommended to isolate such functions in a separate package with bare minimum of code and add a\nfile `.nocover` to exclude the package from coverage calculations. The file should contain\na comment explaining why it is there, for example:\n\n```\n$ cat ./pkg/cassandra/config/.nocover\nrequires connection to Cassandra\n```\n\n## Merging PRs\n**For maintainers:** before merging a PR make sure the title is descriptive and follows [a good commit message](./CONTRIBUTING_GUIDELINES.md)\n\nMerge the PR by using \"Squash and merge\" option on Github. Avoid creating merge commits.\nAfter the merge make sure referenced issues were closed.\n\n## Deprecating CLI Flags\n\n* If a flag is deprecated in release N, it can be removed in release N+2 or three months later, whichever is later.\n* When adding a (deprecated) prefix to the flags, indicate via a deprecation message that the flag could be removed in the future. For example:\n  ```\n  (deprecated, will be removed after 2020-03-15 or in release v1.19.0, whichever is later)\n  ```\n* At the top of the file where the flag name is defined, add a constant and a comment, e.g.\n  ```\n  // TODO deprecated flag to be removed\n  healthCheckHTTPPortWarning = \"(deprecated, will be removed after 2020-03-15 or in release v1.19.0, whichever is later)\"\n  ```\n* Use that constant as the prefix to the help text, e.g.\n  ```\n  flagSet.Int(healthCheckHTTPPort, 0, healthCheckHTTPPortWarning+\" see --\"+adminHTTPHostPort)\n  ```\n* When parsing a deprecated flag into config, log a warning with the same deprecation message\n* Take care of deprecated flags in `initFromViper` functions, do not pass them to business functions.\n\n### Removing Deprecated CLI Flags\n* Ensure all references to the flag's variables have been removed in code.\n* Ensure a \"Breaking Changes\" entry is added in the [CHANGELOG](./CHANGELOG.md) indicating which CLI flag\nis being removed and which CLI flag should be used in favor of this removed flag.\n\nFor example:\n```\n* Remove deprecated flags `--old-flag`, please use `--new-flag` ([#1234](<pull-request URL>), [@myusername](https://github.com/myusername))\n```\n\n## Using Feature Gates for Breaking Changes\n\nAs much as possible, use OTel Collector's [feature gates][feature_gates] to manage breaking changes. For example, consider that we discovered a bug in the existing behavior, such as https://github.com/jaegertracing/jaeger/issues/5270. Simply changing the behavior might be a breaking change, so we implement a new behavior and create an internal config setting that enables or disables it. But how will users ever know and be encouraged to migrate to the new behavior? For that we can create a feature gate (without even creating any additional user-facing configuration), as follows:\n  * Introduce a new feature gate, with the name `jaeger.***`.\n  * If we don't want to change the default behavior right away, we can start the feature in the Alpha state, where it is disabled by default. No breaking changes need to be called out in the changelog.\n  * If we do want to change the default behavior right away, we can start the feature in the Beta state, where it is enabled by default, but the user can still disable it. Call out a breaking change in the changelog.\n  * Two releases later change the gate to Stable, where it is not only enabled by default, but trying to disable it will cause a runtime error. The code for the old behavior should be removed. Call out a breaking change in the changelog.\n  * Two releases later remove the feature gate as unused. Call out a breaking change in the changelog.\n\nSee https://github.com/jaegertracing/jaeger/pull/6441 for an example of this workflow.\n\n[feature_gates]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md\n"
  },
  {
    "path": "CONTRIBUTING_GUIDELINES.md",
    "content": "# How to Contribute to Jaeger\n\nWe'd love your help!\n\nJaeger is [Apache 2.0 licensed](./LICENSE) and accepts contributions via GitHub\npull requests. This document outlines some of the conventions on development\nworkflow, commit message formatting, contact points and other resources to make\nit easier to get your contribution accepted.\n\nWe gratefully welcome improvements to documentation as well as to code.\n\nTable of Contents:\n\n* [Making a Change](#making-a-change)\n* [AI Usage Policy](#ai-usage-policy)\n* [Pull Request Limits for New Contributors](#pull-request-limits-for-new-contributors)\n* [License](#license)\n* [Certificate of Origin - Sign your work](#certificate-of-origin---sign-your-work)\n* [Branches](#branches)\n\n## Making a Change\n\n### Open an issue first\n\n**Before making any significant changes, please open an issue**. Each issue\nshould describe the following:\n\n* Requirement - what kind of business use case are you trying to solve?\n* Problem - what in Jaeger blocks you from solving the requirement?\n* Proposal - what changes do you propose to solve the problem or improve the existing situation?\n* Any open questions to address\n\nDiscussing your proposed changes ahead of time will make the contribution\nprocess smooth for everyone. Once the approach is agreed upon, make your changes\nand open a pull request (PR).\n\n### Assigning Issues\n\nWe do not assign issues to contributors. It is almost never the case that multiple\npeople jump on the same issue, and practice showed that occasionally people who ask\nfor an issue to be assigned to them later have a change in priorities and are unable\nto find time to finish it, which leaves the issue in limbo.\nSo if you have a desire to work on an issue, feel free to mention it in the comment and just submit a PR.\n\n### Creating a pull request\n\nIf you are new to GitHub's contribution workflow, we recommend the following setup:\n  * Go to the respective Jaeger repo on GitHub and create a fork using the button at the top. Select a destination org where you have write permissions (usually it is your personal \"org\").\n  * Clone the fork into your workspace.\n  * (Recommended): register upstream repo as remote\n    * After you clone your forked repo, running below command\n      ```bash\n      git remote -v\n      ```\n      will show `origin`, e.g. `origin git@github.com:{username}/jaeger.git`\n    * Add `upstream` remote:\n      ```bash\n      git remote add upstream git@github.com:jaegertracing/jaeger.git\n      ```\n    * Fetch it:\n      ```bash\n      git fetch upstream main\n      ```\n    * Repoint your main branch:\n      ```bash\n      git branch --set-upstream-to=upstream/main main\n      ```\n    * With this setup, you will not need to keep your main branch in the fork in sync with the upstream repo.\n\nOnce you're ready to make changes:\n  * Create a new local branch (DO NOT make changes to `main`, it will cause CI errors).\n  * Commit your changes, making sure **each commit is signed** ([see below](#certificate-of-origin---sign-your-work)):\n    ```bash\n    git commit -s -m \"Your commit message\"\n    ```\n  * You do not need to squash the commits, it will happen once the PR is merged into the official repo (but each individual commit must be signed).\n  * When satisfied, push the changes. Git will likely ask for upstream destination, so you push commits like this:\n    ```bash\n    git push --set-upstream origin {branch-name}\n    ```\n  * After you push, look for the output, it usually contains a URL to create a pull request.\n  * After raising a PR, please refrain from repeatedly merging the upstream main branch into your feature branch unless you're specifically resolving merge conflicts or updating for critical changes. Each merge triggers a reset of CI checks, requiring maintainers to re-approve your PR, which adds unnecessary overhead.\n\nEach PR should have:\n\n* A descriptive title, known as [\"commit message\"][good-commit-msg]. In summary:\n  * Limit the title to 50 characters\n  * Capitalize the title\n  * Do not end the title with a period\n  * Use the imperative mood in the title\n* A description of the problem it is solving. It could be simply a reference to the corresponding issue, e.g. `Resolves #123`.\n* A summary of changes made to solve the problem. Explain _what_ and _why_ instead of _how_.\n\n## AI Usage Policy\n\n### Goals\n\nThis policy exists to:\n\n- **Keep the effort balanced** – Before AI, contributors did most of the work (writing, testing, understanding). We want to keep it that way. AI should help you, not replace your effort.\n- **Protect maintainer time** – Large, low-quality AI-generated PRs shift the burden to reviewers. We want to avoid that.\n- **Ensure understanding** – Contributors should understand and be able to explain every change they submit.\n- **Keep conversations human** – Code review is a discussion between people, not bots.\n\n### Good use of AI\n\n- Using AI to help you understand the code.\n- Using AI to write drafts of code, tests, or docs.\n- Using AI to explore ideas or try different approaches.\n- Saying \"AI helped me write this\" in your PR description.\n\n### Disallowed use of AI\n\n- Copy-pasting AI output without reading or understanding it.\n- Submitting AI-generated code without testing.\n- Using AI to reply to review comments – reviewers want to talk to you, not a bot.\n\n### Your responsibility\n\n- You own everything you submit, even if AI wrote it.\n- You must understand your code well enough to explain it.\n- You must run tests locally before opening or updating a PR.\n- If AI wrote a big part of your PR, mention that in the PR description.\n\n### Enforcement\n\n- PRs that look like low-effort AI slop will be closed.\n- Repeated violators may be banned from the project.\n\n## Pull Request Limits for New Contributors\n\nTo ensure high-quality code reviews and long-term codebase stability, we limit the number of simultaneous open PRs for new contributors.\n\n### The Policy\n\nYour limit of **simultaneous open PRs** is based on your history with this project:\n\n| Merged PRs in this project | Max Simultaneous Open PRs |\n| :--- | :--- |\n| **0** (First-time contributor) | **1** |\n| **1** merged PR | **2** |\n| **2** merged PRs | **3** |\n| **3+** merged PRs | **Unlimited** |\n\n### Why We Do This\n\nAI tools have dramatically reduced the cost of creating pull requests, but the burden on maintainers for reviewing them remains the same. Large-scale or complex refactors require significant effort to review, and a high volume of PRs from new contributors often leads to:\n\n* **Review Bottlenecks:** Quality reviews take time. A flood of PRs prevents us from giving any single PR the attention it deserves.\n* **Context Fragmentation:** Refactoring legacy code requires deep understanding. We prefer to work with you on one area of the code at a time to ensure the architectural direction is correct.\n* **Reduced Noise:** This policy helps us distinguish between intentional improvements and automated \"bulk\" refactors that may introduce subtle regressions.\n\n### How to Proceed\n\nIf you reach your limit, please focus on addressing feedback and merging your existing PR(s) before opening new ones. PRs opened in excess of these limits may be labeled as **on-hold** or closed to keep our backlog manageable.\n\n### Tips for Success\n\n* **Verify AI Output:** If you use AI tools to assist in refactoring, you are responsible for manually verifying every line. We expect contributors to be able to explain the \"why\" behind every change.\n* **Small over Large:** Smaller, atomic PRs are much more likely to be merged quickly than large refactors.\n\n## License\n\nBy contributing your code, you agree to license your contribution under the\nterms of the [Apache License](./LICENSE).\n\n### Copyright Header\n\nIf you are adding a new file it should have a header like below. In some\nlanguages, e.g. Python, you may need to change the comments to start with `#`.\nThe easiest way is to copy the header from one of the existing source files and\nmake sure the year is current and the copyright says \"The Jaeger Authors\".\n\n```\n// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n```\n\n**Never remove existing copyright headers**. \n\nSome files may have other copyright headers, such as:\n```\n// Copyright (c) 2017 Uber Technologies, Inc.\n```\n\nIf you are modifying such a file you may add Jaeger copyright on top:\n```\n// Copyright (c) 2026 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n```\n\n## Certificate of Origin - Sign your work\n\nBy contributing to this project you agree to the\n[Developer Certificate of Origin](https://developercertificate.org/) (or simply\n[DCO](./DCO)). This document was created by the Linux Kernel community and is a\nsimple statement that you, as a contributor, have the legal right to make the\ncontribution.\n\nThe sign-off is a simple line at the end of the explanation for the patch, which\ncertifies that you wrote it or otherwise have the right to pass it on as an\nopen-source patch. The rules are pretty simple: if you can certify the\nconditions in the [DCO](./DCO), then just add a line to every git commit\nmessage:\n\n    Signed-off-by: Bender Bending Rodriguez <bender.is.great@gmail.com>\n\nusing your real name (sorry, no pseudonyms or anonymous contributions.) You can\nadd the sign off when creating the git commit via `git commit -s`.\n\n### Missing sign-offs\n\nNote that **every commit in the pull request must be signed**. Jaeger\nrepositories are configured with a [DCO-bot][dco-bot] that will check sign-offs\non every commit and block the PR from being merged if some commits are missing\nsign-offs. If you only have one commit or the latest commit in the PR is missing\na sign-off, the simplest way to fix this is to run:\n\n```\ngit commit --amend -s\n```\n\nwhich will prompt you to edit the commit message while adding a signature.\nSimply accept the text as is, and push the branch:\n\n```\ngit push --force\n```\n\nIf some commit in the middle of your commit history is missing the sign-off, the\nsimplest solution is to squash the commits into one and sign it. For example,\nsuppose that your branch history looks like this:\n\n```\nfe43631 - Fix HotROD Docker command\n933efb3 - Add files for ingester\n214c133 - Rename gas to gosec\n0a40309 - Update Makefile build_ui target to lerna structure\n7919cd9 - Add support for Cassandra reconnect interval\na0dc40e - Fix deploy step\n77a0573 - (tag: v1.6.0) Prepare release 1.6.0\n```\n\nLet's assume that the first commit `77a0573` was the commit before you started\nwork on your PR, and commits from `a0dc40e` to `fe43631` are your changes that\nyou want to squash. You can run the soft reset command:\n\n```\ngit reset --soft 77a0573\n```\n\nIt will undo all changes after commit `77a0573` and stage them. You can commit\nthem all at once while adding the signature:\n\n```\ngit commit -s -m 'your commit message, e.g. the PR title'\n```\n\nThen push the branch:\n\n```\ngit push --force\n```\n\n[good-commit-msg]: https://chris.beams.io/posts/git-commit/\n[dco-bot]: https://github.com/probot/dco#how-it-works\n\n## Branches\n\nBefore submitting a PR make sure to create a named branch in your forked repository. Our CI will fail if you submit a PR from the `main` branch. If that happens, just create a new branch and re-submit the PR from that branch.\n"
  },
  {
    "path": "DCO",
    "content": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 York Street, Suite 102,\nSan Francisco, CA 94110 USA\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n\n"
  },
  {
    "path": "GOVERNANCE.md",
    "content": "# Jaeger Governance\n\nThis document defines governance policies for the Jaeger project.\n\n## Maintainers\n\nJaeger Maintainers have write access to the Jaeger GitHub repository https://github.com/jaegertracing/jaeger.\nThey can merge their own patches or patches from others. The current maintainers can be found in [MAINTAINERS](./MAINTAINERS.md).\n\nThis privilege is granted with some expectation of responsibility: maintainers are people who care about the Jaeger project and want to help it grow and improve. A maintainer is not just someone who can make changes, but someone who has demonstrated his or her ability to collaborate with the team, get the most knowledgeable people to review code, contribute high-quality code, and follow through to fix issues (in code or tests).\n\nA maintainer is a contributor to the Jaeger project's success and a citizen helping the project succeed.\n\n## Becoming a Maintainer\n\nTo become a maintainer you need to demonstrate the following:\n\n  * commitment to the project\n    * participate in discussions, contributions, code reviews for 3 months or more,\n    * perform code reviews for 10 non-trivial pull requests,\n    * contribute 10 non-trivial pull requests and have them merged into the `main` branch,\n  * ability to write good code,\n  * ability to collaborate with the team,\n  * understanding of how the team works (policies, processes for testing and code review, etc),\n  * understanding of the projects' code base and coding style.\n\nA new maintainer must be proposed by an existing maintainer by sending a message to the\n[jaeger-tracing@googlegroups.com](https://groups.google.com/forum/#!forum/jaeger-tracing)\nmailing list, or by opening an issue on GitHub, containing the following information:\n\n  * nominee's first and last name and GitHub user name,\n  * an explanation of why the nominee should be a committer,\n  * a list of links to non-trivial pull requests (top 10) authored by the nominee.\n\nTwo other maintainers need to second the nomination. If no one objects in 5 working days (U.S.), the nomination is accepted.  If anyone objects or wants more information, the maintainers discuss and usually come to a consensus (within the 5 working days). If issues can't be resolved, there's a simple majority vote among current maintainers.\n\n## Maintainer duties\n\nMaintainers are required to participate in the project, by joining discussions, submitting and reviewing pull requests, answering user questions, among others.\n\nBesides that, we have one concrete activity in which maintainers have to engage from time to time: releasing new versions of Jaeger. This process ideally takes only a couple of hours, but requires coordination on different fronts. Even though the process is well documented, it is not without eventual glitches, so, each release needs a \"Release Manager\". How it works is described in the [RELEASE.md](RELEASE.md) file.\n\nMaintainers are also encouraged to speak about Jaeger at conferences, especially KubeCon+CloudNativeCon which happens twice a year. This event has a \"maintainer track\", in which maintainers can give an introduction and/or a deep dive about their projects. The Jaeger project has always participated since it became part of the CNCF.\n\n## Changes in Maintainership\n\nWe do not expect anyone to make a permanent commitment to be a Jaeger maintainer forever. After all, circumstances change,\npeople get new jobs, new interests, and may not be able to continue contributing to the project. At the same time, we need\nto keep the list of maintainers current in order to have effective governance. People may be removed from the current list\nof maintainers via one of the following ways:\n  * They can resign\n  * If they stop contributing to the project for a period of 6 months or more\n  * By a 2/3 majority vote by maintainers\n\nFormer maintainers can be reinstated to full maintainer status through the same process of\n[Becoming a Maintainer](#becoming-a-maintainer) as first-time nominees.\n\n## Emeritus Maintainers\n\nFormer maintainers are recognized with an honorary _Emeritus Maintainer_ status, and have their names permanently listed in the [MAINTAINERS](./MAINTAINERS.md#emeritus-maintainers) file as a form of gratitude for their contributions.\n\n## GitHub Project Administration\n\nMaintainers will be added to the GitHub @jaegertracing/jaeger-maintainers team, and made a GitHub maintainer of that team. They will be given write permission to the Jaeger GitHub repository https://github.com/jaegertracing/jaeger.\n\n## Changes in Governance\n\nAll changes in Governance require a 2/3 majority vote by maintainers.\n\n## Other Changes\n\nUnless specified above, all other changes to the project require a 2/3 majority vote by maintainers.\nAdditionally, any maintainer may request that any change require a 2/3 majority vote by maintainers.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS.md",
    "content": "The current Maintainers Group for the Jaeger Project consists of:\n\n| Name | Employer | Responsibilities |\n| ---- | -------- | ---------------- |\n| [@albertteoh](https://github.com/albertteoh) | PackSmith | ALL | \n| [@jkowall](https://github.com/jkowall) | Paessler | ALL |\n| [@joe-elliott](https://github.com/joe-elliott) | Grafana Labs | ALL |\n| [@mahadzaryab1](https://github.com/mahadzaryab1) | Bloomberg | ALL |\n| [@pavolloffay](https://github.com/pavolloffay) | RedHat | ALL |\n| [@yurishkuro](https://github.com/yurishkuro) | Meta | ALL |\n\nThis list must be kept in sync with the [CNCF Project Maintainers list](https://github.com/cncf/foundation/blob/master/project-maintainers.csv).\n\nSee [the project Governance](./GOVERNANCE.md) for how maintainers are selected and replaced.\n\n### Emeritus Maintainers\n\nWe are grateful to our former maintainers for their contributions to the Jaeger project.\n\n* [@black-adder](https://github.com/black-adder)\n* [@jpkrohling](https://github.com/jpkrohling)\n* [@objectiser](https://github.com/objectiser)\n* [@tiffon](https://github.com/tiffon)\n* [@vprithvi](https://github.com/vprithvi)\n\n### Maintainer Onboarding\n\nUpon approval, the following steps should be taken to onboard the new maintainer:\n\n*   **1. Update Project Documentation**\n    *   **`MAINTAINERS.md` File:** Merge the PR to add the new maintainer to the `MAINTAINERS.md` file(s) in the relevant Jaeger repositories.\n*   **2. Grant Permissions**\n    *   **GitHub:** Add the new maintainer to the `@jaegertracing/jaeger-maintainers` GitHub team. This grants them write access to the Jaeger repositories.\n    *   **CNCF Mailing List:** Add the new maintainer to the `cncf-jaeger-maintainers@lists.cncf.io` mailing list (and any other relevant Jaeger mailing lists). Contact the existing `cncf-jaeger-maintainers` to find out the precise process for adding to the mailing list, it will likely involve getting in touch with the CNCF.\n    *   **CNCF Maintainer Registry:**\n        *   Create a PR against the `cncf/foundation` repository to add the new maintainer's information to the `project-maintainers.csv` file. The following fields are required:\n        *   Reference the PR in the `cncf-jaeger-maintainers` mailing list.\n    *   **Signing Keys:**\n        *   Jaeger uses a GPG key for encrypted emails sent to the maintainers for security reports along with access to the `maintainers-only` GitHub repository. This key is stored in our 1password repository. \n    *   **1Password:** Connect with an existing maintainer to be added to our jaegertracing 1Password team.\n    *   **Netlify:** Is where our website is served from, an existing maintainer can add you to our team.\n    *   We use two analytics tools, one is **Scarf** and the other is **Google Analytics** for the projects public facing sites and downloads. \n*   **3. Announcement**\n    *   Announce the new maintainer to the Jaeger community through the mailing list, blog, or other appropriate channels.\n\n### Maintainer Offboarding\n\nThe process for removing a maintainer is similar to adding one. A maintainer can step down voluntarily or be removed by a vote of the other maintainers if they are no longer fulfilling their responsibilities or are violating the project's Code of Conduct. A supermajority vote is needed to remove a maintainer. Their access should be revoked from all relevant tools, and the project documentation updated accordingly."
  },
  {
    "path": "Makefile",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nSHELL := /bin/bash\nJAEGER_IMPORT_PATH = github.com/jaegertracing/jaeger\n\n# PLATFORMS is a list of all supported platforms\nPLATFORMS=\"linux/amd64,linux/arm64,linux/s390x,linux/ppc64le,darwin/amd64,darwin/arm64,windows/amd64\"\nLINUX_PLATFORMS=$(shell echo \"$(PLATFORMS)\" | tr ',' '\\n' | grep linux | tr '\\n' ',' | sed 's/,$$/\\n/')\n\n# SRC_ROOT is the top of the source tree.\nSRC_ROOT := $(shell git rev-parse --show-toplevel)\n\nifeq ($(DEBUG_BINARY),)\n\tDISABLE_OPTIMIZATIONS =\n\tSUFFIX =\n\tTARGET = release\nelse\n\tDISABLE_OPTIMIZATIONS = -gcflags=\"all=-N -l\"\n\tSUFFIX = -debug\n\tTARGET = debug\nendif\n\n\n# All .go files that are not auto-generated and should be auto-formatted and linted.\nALL_SRC = $(shell find . -name '*.go' \\\n\t\t\t\t   -not -name '_*' \\\n\t\t\t\t   -not -name '.*' \\\n\t\t\t\t   -not -name 'mocks*' \\\n\t\t\t\t   -not -name '*.pb.go' \\\n\t\t\t\t   -not -path './vendor/*' \\\n\t\t\t\t   -not -path './idl/*' \\\n\t\t\t\t   -not -path './internal/tools/*' \\\n\t\t\t\t   -not -path './scripts/build/docker/debug/*' \\\n\t\t\t\t   -not -path '*/mocks/*' \\\n\t\t\t\t   -type f | \\\n\t\t\t\tsort)\n\n# All .sh or .py or Makefile or .mk files that should be auto-formatted and linted.\nSCRIPTS_SRC = $(shell find . \\( -name '*.sh' -o -name '*.py' -o -name '*.mk' -o -name 'Makefile*' -o -name 'Dockerfile*' \\) \\\n\t\t\t\t\t\t-not -path './.git/*' \\\n\t\t\t\t\t\t-not -path './vendor/*' \\\n\t\t\t\t\t\t-not -path './idl/*' \\\n\t\t\t\t\t\t-not -path './jaeger-ui/*' \\\n\t\t\t\t\t\t-type f | \\\n\t\t\t\t\tsort)\n\n# ALL_PKGS is used with 'nocover' and 'goleak'\nALL_PKGS = $(shell echo $(dir $(ALL_SRC)) | tr ' ' '\\n' | grep -v '/.*-gen/' | sort -u)\n\nGO=go\nGOOS ?= $(shell $(GO) env GOOS)\nGOARCH ?= $(shell $(GO) env GOARCH)\n\n# go test does not support -race flag on s390x architecture\nifeq ($(GOARCH), s390x)\n\tRACE=\nelse\n\tRACE=-race\nendif\n# sed on Mac does not support the same syntax for in-place updates as sed on Linux\n# When running on MacOS it's best to install gsed and run Makefile with SED=gsed.\n# We want the actual OS here, not what GOOS may have been set to by recursive make calls.\nifeq ($(shell GOOS= $(GO) env GOOS),darwin)\n\tSED=gsed\nelse\n\tSED=sed\nendif\n\nGOTEST_QUIET=$(GO) test $(RACE)\nGOTEST=$(GOTEST_QUIET) -v\nCOVEROUT=cover.out\nGOFMT=gofmt\nFMT_LOG=.fmt.log\nIMPORT_LOG=.import.log\nCOLORIZE ?= | $(SED) 's/PASS/✅ PASS/g' | $(SED) 's/FAIL/❌ FAIL/g' | $(SED) 's/SKIP/🔕 SKIP/g'\n\n # import other Makefiles after the variables are defined\n\ninclude scripts/makefiles/BuildBinaries.mk\ninclude scripts/makefiles/BuildInfo.mk\ninclude scripts/makefiles/Docker.mk\ninclude scripts/makefiles/IntegrationTests.mk\ninclude scripts/makefiles/Protobuf.mk\ninclude scripts/makefiles/Tools.mk\ninclude scripts/makefiles/Windows.mk\n\n\n.DEFAULT_GOAL := test-and-lint\n\n.PHONY: test-and-lint\ntest-and-lint: test fmt lint\n\n.PHONY: echo-version\necho-version:\n\t@echo \"$(GIT_CLOSEST_TAG)\"\n\n.PHONY: echo-platforms\necho-platforms:\n\t@echo \"$(PLATFORMS)\"\n\n.PHONY: echo-linux-platforms\necho-linux-platforms:\n\t@echo \"$(LINUX_PLATFORMS)\"\n\n.PHONY: echo-all-pkgs\necho-all-pkgs:\n\t@echo $(ALL_PKGS) | tr ' ' '\\n' | sort\n\n.PHONY: echo-all-srcs\necho-all-srcs:\n\t@echo $(ALL_SRC) | tr ' ' '\\n' | sort\n\n.PHONY: clean\nclean:\n\trm -rf cover*.out .cover/ cover.html $(FMT_LOG) $(IMPORT_LOG) \\\n\t\tjaeger-ui/packages/jaeger-ui/build\n\tfind ./cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual -type f -name '*.gz' -delete\n\tGOCACHE=$(GOCACHE) go clean -cache -testcache\n\tbash scripts/build/clean-binaries.sh\n\n.PHONY: test\ntest:\n\tbash -c \"set -e; set -o pipefail; $(GOTEST) -tags=memory_storage_integration ./... $(COLORIZE)\"\n\n.PHONY: cover\ncover: nocover\n\tbash -c \"set -e; set -o pipefail; STORAGE=memory $(GOTEST) -timeout 5m -coverprofile $(COVEROUT) ./... | tee test-results.json\"\n\tgo tool cover -html=cover.out -o cover.html\n\n.PHONY: nocover\nnocover:\n\t@echo Verifying that all packages have test files to count in coverage\n\t@scripts/lint/check-test-files.sh $(ALL_PKGS)\n\n.PHONY: fmt\nfmt: $(GOFUMPT)\n\t@echo Running import-order-cleanup on ALL_SRC ...\n\t@./scripts/lint/import-order-cleanup.py -o inplace -t $(ALL_SRC)\n\t@echo Running gofmt on ALL_SRC ...\n\t@$(GOFMT) -e -s -l -w $(ALL_SRC)\n\t@echo Running gofumpt on ALL_SRC ...\n\t@$(GOFUMPT) -e -l -w $(ALL_SRC)\n\t@echo Running updateLicense.py on ALL_SRC ...\n\t@./scripts/lint/updateLicense.py $(ALL_SRC) $(SCRIPTS_SRC)\n\n.PHONY: lint\nlint: lint-fmt lint-license lint-imports lint-semconv lint-goversion lint-goleak lint-go\n\n.PHONY: lint-license\nlint-license:\n\t@echo Verifying that all files have license headers\n\t@./scripts/lint/updateLicense.py $(ALL_SRC) $(SCRIPTS_SRC) > $(FMT_LOG)\n\t@[ ! -s \"$(FMT_LOG)\" ] || (echo \"License check failures, run 'make fmt'\" | cat - $(FMT_LOG) && false)\n\n.PHONY: lint-nocommit\nlint-nocommit:\n\t@if git diff origin/main | grep '@no''commit' ; then \\\n\t\techo \"❌ Cannot merge PR that contains @no\"\"commit string\" ; \\\n\t\tGIT_PAGER=cat git diff -G '@no''commit' origin/main ; \\\n\t\tfalse ; \\\n\telse \\\n\t\techo \"✅ Changes do not contain @no\"\"commit string\" ; \\\n\tfi\n\n.PHONY: lint-imports\nlint-imports:\n\t@echo Verifying that all Go files have correctly ordered imports\n\t@./scripts/lint/import-order-cleanup.py -o stdout -t $(ALL_SRC) > $(IMPORT_LOG)\n\t@[ ! -s \"$(IMPORT_LOG)\" ] || (echo \"Import ordering failures, run 'make fmt'\" | cat - $(IMPORT_LOG) && false)\n\n.PHONY: lint-fmt\nlint-fmt: $(GOFUMPT)\n\t@echo Verifying that all Go files are formatted with gofmt and gofumpt\n\t@rm -f $(FMT_LOG)\n\t@$(GOFMT) -d -e -s $(ALL_SRC) > $(FMT_LOG) || true\n\t@$(GOFUMPT) -d -e $(ALL_SRC) >> $(FMT_LOG) || true\n\t@[ ! -s \"$(FMT_LOG)\" ] || (echo \"Formatting check failed. Please run 'make fmt'\" && head -100 $(FMT_LOG) && false)\n\n.PHONY: lint-semconv\nlint-semconv:\n\t./scripts/lint/check-semconv-version.sh\n\n.PHONY: lint-goversion\nlint-goversion:\n\t./scripts/lint/check-go-version.sh\n\n.PHONY: lint-goleak\nlint-goleak:\n\t@echo Verifying that all packages with tests have goleak in their TestMain\n\t@scripts/lint/check-goleak-files.sh $(ALL_PKGS)\n\n.PHONY: lint-go\nlint-go: $(LINT)\n\t$(LINT) -v run\n\n.PHONY: govulncheck\ngovulncheck: $(GOVULNCHECK)\n\t$(GOVULNCHECK) ./...\n\n.PHONY: lint-jaeger-idl-versions\nlint-jaeger-idl-versions:\n\t@echo \"checking jaeger-idl version mismatch between git submodule and go.mod dependency\"\n\t@./scripts/lint/check-jaeger-idl-version.sh\n\n.PHONY: run-all-in-one\nrun-all-in-one: build-ui\n\tgo run ./cmd/all-in-one --log-level debug\n\n.PHONY: changelog\nchangelog:\n\t@./scripts/release/notes.py --exclude-dependabot --verbose\n\n.PHONY: draft-release\ndraft-release:\n\t./scripts/release/draft.py\n\n.PHONY: prepare-release\nprepare-release:\n\t@if [ -z \"$(VERSION)\" ]; then \\\n\t\techo \"Usage: make prepare-release VERSION=x.x.x\"; \\\n\t\techo \"Example: make prepare-release VERSION=2.14.0\"; \\\n\t\texit 1; \\\n\tfi\n\tbash ./scripts/release/prepare.sh $(VERSION)\n\n.PHONY: test-ci\ntest-ci: GOTEST := $(GOTEST_QUIET)\ntest-ci: build-examples cover\n\n.PHONY: init-submodules\ninit-submodules:\n\tgit submodule update --init --recursive\n\nMOCKERY_FLAGS := --all --disable-version-string\n.PHONY: generate-mocks\ngenerate-mocks: $(MOCKERY)\n\tfind . -path '*/mocks/*' -name '*.go' -type f -delete\n\t$(MOCKERY) | tee .mockery.log\n\n.PHONY: certs\ncerts:\n\tcd internal/config/tlscfg/testdata && ./gen-certs.sh\n\n.PHONY: certs-dryrun\ncerts-dryrun:\n\tcd internal/config/tlscfg/testdata && ./gen-certs.sh -d\n\n.PHONY: repro-check\nrepro-check:\n\t# Check local reproducibility of generated executables.\n\t$(MAKE) clean\n\t$(MAKE) build-all-platforms\n\t# Generate checksum for all executables under ./cmd\n\tfind cmd -type f -executable -exec shasum -b -a 256 {} \\; | sort -k2 | tee sha256sum.combined.txt\n\t$(MAKE) clean\n\t$(MAKE) build-all-platforms\n\tshasum -b -a 256 --strict --check ./sha256sum.combined.txt\n"
  },
  {
    "path": "NOTICE",
    "content": "Jaeger, Distributed Tracing Platform.\n\nCopyright 2015-2019 The Jaeger Project Authors\n\nLicensed under Apache License 2.0.  See LICENSE for terms.\n\nIncludes software developed at Uber Technologies, Inc. (https://eng.uber.com/).\n"
  },
  {
    "path": "README.md",
    "content": "[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua)\n\n<img align=\"right\" width=\"290\" height=\"290\" src=\"https://www.jaegertracing.io/img/jaeger-vector.svg\">\n\n[![Slack chat][slack-img]](#get-in-touch)\n[![Unit Tests][ci-img]][ci]\n[![Coverage Status][cov-img]][cov]\n[![Project+Community stats][community-badge]][community-stats]\n[![FOSSA Status][fossa-img]][fossa]\n[![OpenSSF Scorecard][openssf-img]][openssf]\n[![OpenSSF Best Practices][openssf-bp-img]][openssf-bp] \n[![CLOMonitor][clomonitor-img]][clomonitor]\n[![Artifact Hub][artifacthub-img]][artifacthub]\n\n<img src=\"https://raw.githubusercontent.com/cncf/artwork/main/other/cncf-member/graduated/color/cncf-graduated-color.svg\" width=\"250\">\n\n# Jaeger - a Distributed Tracing System\n\n💥💥💥 Jaeger v2 is out! Read the [blog post](https://medium.com/jaegertracing/jaeger-v2-released-09a6033d1b10) and [try it out](https://www.jaegertracing.io/docs/latest/getting-started/).\n\n## Quick Start\n\nGet Jaeger running in seconds with Docker:\n\n```bash\n# Run Jaeger all-in-one (includes UI, collector, query, and in-memory storage)\ndocker run --rm --name jaeger \\\n  -p 16686:16686 \\\n  -p 4317:4317 \\\n  -p 4318:4318 \\\n  jaegertracing/jaeger:latest\n\n# Access the UI at http://localhost:16686\n# Send traces via OTLP: gRPC on port 4317, HTTP on port 4318\n```\n\nFor production deployments and more options, see the [Getting Started Guide](https://www.jaegertracing.io/docs/latest/getting-started/).\n\n## Architecture\n\n```mermaid\ngraph TD\n    SDK[\"OpenTelemetry SDK\"] --> |HTTP or gRPC| COLLECTOR\n    COLLECTOR[\"Jaeger Collector\"] --> STORE[Storage]\n    COLLECTOR --> |gRPC| PLUGIN[Storage Plugin]\n    COLLECTOR --> |gRPC/sampling| SDK\n    PLUGIN --> STORE\n    QUERY[Jaeger Query Service] --> STORE\n    QUERY --> |gRPC| PLUGIN\n    UI[Jaeger UI] --> |HTTP| QUERY\n    subgraph Application Host\n        subgraph User Application\n            SDK\n        end\n    end\n```\n\nJaeger is a distributed tracing platform created by [Uber Technologies](https://eng.uber.com/distributed-tracing/) and donated to [Cloud Native Computing Foundation](https://cncf.io).\n\nSee Jaeger [documentation][doc] for getting started, operational details, and other information.\n\nJaeger is hosted by the [Cloud Native Computing Foundation](https://cncf.io) (CNCF) as the 7th top-level project, graduated in October 2019. See the CNCF [Jaeger incubation announcement](https://www.cncf.io/blog/2017/09/13/cncf-hosts-jaeger/) and [Jaeger graduation announcement](https://www.cncf.io/announcement/2019/10/31/cloud-native-computing-foundation-announces-jaeger-graduation/).\n\n## Get Involved\n\nJaeger is an open source project with open governance. We welcome contributions from the community, and we would love your help to improve and extend the project. Here are [some ideas](https://www.jaegertracing.io/get-involved/) for how to get involved. Many of them do not even require any coding.\n\n## Version Compatibility Guarantees\n\nSince Jaeger uses many components from the [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector/) we try to maintain configuration compatibility between Jaeger releases. Occasionally, configuration options in Jaeger (or in Jaeger v1 CLI flags) can be deprecated due to usability improvements, new functionality, or changes in our dependencies.\nIn such situations, developers introducing the deprecation are required to follow [these guidelines](./CONTRIBUTING.md#deprecating-cli-flags).\n\nIn short, for a deprecated configuration option, you should expect to see the following message in the documentation or release notes:\n```\n(deprecated, will be removed after yyyy-mm-dd or in release vX.Y.Z, whichever is later)\n```\n\nA grace period of at least **3 months** or **two minor version bumps** (whichever is later) from the first release\ncontaining the deprecation notice will be provided before the deprecated configuration option _can_ be deleted.\n\nFor example, consider a scenario where v2.0.0 is released on 01-Sep-2024 containing a deprecation notice for a configuration option.\nThis configuration option will remain in a deprecated state until the later of 01-Dec-2024 or v2.2.0 where it _can_ be removed on or after either of those events.\nIt may remain deprecated for longer than the aforementioned grace period.\n\n## Go Version Compatibility Guarantees\n\nThe Jaeger project attempts to track the currently supported versions of Go, as [defined by the Go team](https://go.dev/doc/devel/release#policy).\nRemoving support for an unsupported Go version is not considered a breaking change.\n\nStarting with the release of Go 1.21, support for Go versions will be updated as follows:\n\n1. Soon after the release of a new Go minor version `N`, updates will be made to the build and tests steps to accommodate the latest Go minor version.\n2. Soon after the release of a new Go minor version `N`, support for Go version `N-1` will be removed and version `N` will become the minimum required version.\n\nNote: All importable code has been moved to internal packages, so there is no need to maintain backward compatibility with older compilers (previously version `N-1` was used).\n\n## Related Repositories\n\n### Components\n\n * [UI](https://github.com/jaegertracing/jaeger-ui)\n * [Data model](https://github.com/jaegertracing/jaeger-idl)\n\n### Documentation\n\n  * Published: https://www.jaegertracing.io/docs/\n  * Source: https://github.com/jaegertracing/documentation\n\n## Building From Source\n\nSee [CONTRIBUTING](./CONTRIBUTING.md).\n\n## Contributing\n\nSee [CONTRIBUTING](./CONTRIBUTING.md).\n\nThanks to all the people who already contributed!\n\n<a href=\"https://github.com/jaegertracing/jaeger/graphs/contributors\">\n  <img src=\"https://contributors-img.web.app/image?repo=jaegertracing/jaeger\" />\n</a>\n\n### Maintainers\n\nRules for becoming a maintainer are defined in the [GOVERNANCE](./GOVERNANCE.md) document.\nThe official maintainers of the Jaeger project are listed in the [MAINTAINERS](./MAINTAINERS.md) file.\nPlease use `@jaegertracing/jaeger-maintainers` to tag them on issues / PRs.\n\nSome repositories under [jaegertracing](https://github.com/jaegertracing) org have additional maintainers.\n\n## Project Status Meetings\n\nThe Jaeger maintainers and contributors meet regularly on a video call. Everyone is welcome to join, including end users. For meeting details, see https://www.jaegertracing.io/get-in-touch/.\n\n## Roadmap\n\nSee https://www.jaegertracing.io/docs/roadmap/\n\n## Get in Touch\n\nHave questions, suggestions, bug reports? Reach the project community via these channels:\n\n * [Slack chat room `#jaeger`][slack] (need to join [CNCF Slack][slack-join] for the first time)\n * [`jaeger-tracing` mail group](https://groups.google.com/forum/#!forum/jaeger-tracing)\n * GitHub [issues](https://github.com/jaegertracing/jaeger/issues) and [discussions](https://github.com/jaegertracing/jaeger/discussions)\n\n## Security\n\nThird-party security audits of Jaeger are available in https://github.com/jaegertracing/security-audits. Please see [Issue #1718](https://github.com/jaegertracing/jaeger/issues/1718) for the summary of available security mechanisms in Jaeger.\n\n## Adopters\n\nJaeger as a product consists of multiple components. We want to support different types of users,\nwhether they are only using our instrumentation libraries or full end to end Jaeger installation,\nwhether it runs in production or you use it to troubleshoot issues in development.\n\nPlease see [ADOPTERS.md](./ADOPTERS.md) for some of the organizations using Jaeger today.\nIf you would like to add your organization to the list, please comment on our\n[survey issue](https://github.com/jaegertracing/jaeger/issues/207).\n\n## Sponsors\n\nThe Jaeger project owes its success in open source largely to the Cloud Native Computing Foundation (CNCF), our primary supporter. We deeply appreciate their vital support.  Furthermore, we are grateful to Uber for their initial, project-launching donation, and for the continuous contributions of software and infrastructure from 1Password, Codecov.io, Dosu, GitHub, Google Analytics, Netlify, and Oracle Cloud Infrastructure. Thank you for your generous support.\n\n## License\n\nCopyright (c) The Jaeger Authors. [Apache 2.0 License](./LICENSE).\n\n[doc]: https://jaegertracing.io/docs/\n[ci-img]: https://github.com/jaegertracing/jaeger/actions/workflows/ci-unit-tests.yml/badge.svg?branch=main\n[ci]: https://github.com/jaegertracing/jaeger/actions/workflows/ci-unit-tests.yml?query=branch%3Amain\n[cov-img]: https://codecov.io/gh/jaegertracing/jaeger/branch/main/graph/badge.svg\n[cov]: https://codecov.io/gh/jaegertracing/jaeger/branch/main/\n[fossa-img]: https://app.fossa.com/api/projects/git%2Bgithub.com%2Fjaegertracing%2Fjaeger.svg?type=shield\n[fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Fjaegertracing%2Fjaeger?ref=badge_shield\n[openssf-img]: https://api.securityscorecards.dev/projects/github.com/jaegertracing/jaeger/badge\n[openssf]: https://securityscorecards.dev/viewer/?uri=github.com/jaegertracing/jaeger\n[openssf-bp-img]: https://www.bestpractices.dev/projects/1273/badge\n[openssf-bp]: https://www.bestpractices.dev/projects/1273\n[clomonitor-img]: https://img.shields.io/endpoint?url=https://clomonitor.io/api/projects/cncf/jaeger/badge\n[clomonitor]: https://clomonitor.io/projects/cncf/jaeger\n[artifacthub-img]: https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/jaegertracing\n[artifacthub]: https://artifacthub.io/packages/search?repo=jaegertracing\n\n\n[community-badge]: https://img.shields.io/badge/Project+Community-stats-blue.svg\n[community-stats]: https://all.devstats.cncf.io/d/54/project-health?orgId=1&var-repogroup_name=Jaeger\n[hotrod-tutorial]: https://medium.com/jaegertracing/take-jaeger-for-a-hotrod-ride-233cf43e46c2\n[slack]: https://cloud-native.slack.com/archives/CGG7NFUJ3\n[slack-join]: https://slack.cncf.io\n[slack-img]: https://img.shields.io/badge/slack-join%20chat%20%E2%86%92-brightgreen?logo=slack\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Jaeger Overall Release Process\n\n## ⭐ Start Here: Create Tracking Issue for Release ⭐\n\nRun the following command to create a tracking issue with the full checklist:\n\n```bash\nbash scripts/release/start.sh\n```\n\nThis script will:\n- Automatically determine the next version number (e.g., v2.14.0)\n- Create a GitHub issue with the complete release checklist\n- Include the exact automation commands with the correct version numbers\n\nExample output:\n```\nCurrent version: v2.13.0\nNew version: v2.14.0\n...\nCreating issue in jaegertracing/jaeger\nhttps://github.com/jaegertracing/jaeger/issues/7757\n```\n\n## 📝 Release Steps\n\nFollow the checklist in the created tracking issue. The high level steps are:\n\n1. Perform UI release according to <https://github.com/jaegertracing/jaeger-ui/blob/main/RELEASE.md>\n2. Perform Backend release (see below)\n3. [Publish documentation](https://github.com/jaegertracing/documentation/blob/main/RELEASE.md) for the new version on `jaegertracing.io`.\n\n# ⚙️ Jaeger Backend Release Process\n\n<!-- BEGIN_CHECKLIST -->\n\n1. Create a PR \"Prepare release vX.Y.Z\" against main or maintenance branch ([example](https://github.com/jaegertracing/jaeger/pull/6826)).\n  * **Automated**:\n    ```bash\n    make prepare-release VERSION=X.Y.Z\n    ```\n    * Updates CHANGELOG.md (generates content via `make changelog`)\n    * Upgrades jaeger-ui submodule to the corresponding version\n    * Rotates release managers table\n    * Creates PR with label `changelog:skip`\n  * Manual: See [Manual release pull request](https://github.com/jaegertracing/jaeger/blob/main/RELEASE.md#manual-release-pull-request).\n2. After the PR is merged, create a release on Github:\n  * **Automated**:\n    ```bash\n    make draft-release\n    ```\n  * Manual: See [Manual release](https://github.com/jaegertracing/jaeger/blob/main/RELEASE.md#manual-release).\n3. Once the release is created, the [Publish Release workflow](https://github.com/jaegertracing/jaeger/actions/workflows/ci-release.yml) will run to build artifacts.\n  * Wait for the workflow to finish. For monitoring and troubleshooting, open the logs of the workflow run from above URL.\n  * Check the images are available on [Docker Hub](https://hub.docker.com/r/jaegertracing/) and binaries are uploaded [to the release](https://github.com/jaegertracing/jaeger/releases).\n\n<!-- END_CHECKLIST -->\n\n## Manual release pull request\n\nCreate a PR \"Prepare release vX.Y.Z\" against main or maintenance branch ([example](https://github.com/jaegertracing/jaeger/pull/6826)).\n\n  * Update CHANGELOG.md to include:\n    * A new section with the header `vX.Y.Z (YYYY-MM-DD)` (copy the template at the top)\n    * A curated list of notable changes and links to PRs. Do not simply dump git log, select the changes that affect the users.\n      To obtain the list of all changes run `make changelog`.\n    * The section can be split into sub-section if necessary, e.g. UI Changes, Backend Changes, Bug Fixes, etc.\n  * Then upgrade the submodule versions and finally commit. For example:\n      ```\n      git submodule init\n      git submodule update\n      pushd jaeger-ui\n      git checkout main\n      git pull\n      git checkout vX.Y.Z  # use the new version\n      popd\n      ```\n      * If there are only dependency bumps, indicate this with \"Dependencies upgrades only\" ([example](https://github.com/jaegertracing/jaeger-ui/pull/2431/files)).\n      * If there are no changes, indicate this with \"No changes\" ([example](https://github.com/jaegertracing/jaeger/pull/4131/files)).\n  * Rotate the below release managers table placing yourself at the bottom. The date should be the first Wednesday of the month.\n  * Commit, push and open a PR.\n  * Add label `changelog:skip` to the pull request.\n\n## Manual release\n\nCreate a release on [GitHub Releases](https://github.com/jaegertracing/jaeger/releases/):\n\n  * Title \"Prepare Release v2.x.x\"\n  * Tag `v2.x.x` (note the `v` prefix) and choose appropriate branch (usually `main`)\n  * Copy the new CHANGELOG.md section into the release notes\n  * Extra: GitHub has a button \"generate release notes\". Those are not formatted as we want,\n    but it has a nice feature of explicitly listing first-time contributors.\n    Before doing the previous step, you can click that button and then remove everything\n    except the New Contributors section. Change the header to `### 👏 New Contributors`,\n    then copy the main changelog above it. [Example](https://github.com/jaegertracing/jaeger/releases/tag/v1.55.0).\n\n## 🔧 Patch Release\n\nSometimes we need to do a patch release, e.g. to fix a newly introduced bug. If the main branch already contains newer changes, it is recommended that a patch release is done from a version branch.\n\nMaintenance branches should follow naming convention: `release-major.minor` (e.g.`release-1.8`).\n\n1. Find the commit in `main` for the release you want to patch (e.g., `a49094c2` for v1.34.0).\n2. `git checkout ${commit}; git checkout -b ${branch-name}`. The branch name should be in the form `release-major.minor`, e.g., `release-1.34`. Push the branch to the upstream repository.\n3. Apply fixes to the branch. The recommended way is to merge the fixes into `main` first and then cherry-pick them into the version branch (e.g., `git cherry-pick c733708c` for the fix going into `v1.34.1`).\n4. Follow the regular process for creating a release (except for the Documentation step).\n   * When creating a release on GitHub, pick the version branch when applying the new tag.\n   * Once the release tag is created, the `ci-release` workflow will kick in and deploy the artifacts for the patch release.\n5. Do not perform a new release of the documentation since the major.minor is not changing. The one change that may be useful is bumping the `binariesLatest` variable in the `config.toml` file ([example](https://github.com/jaegertracing/documentation/commit/eacb52f332a7e069c254e652a6b4a58ea5a07b32)).\n\n## 👥 Release managers\n\nA Release Manager is the person responsible for ensuring that a new version of Jaeger is released. This person will coordinate the required changes, including to the related components such as UI, IDL, and jaeger-lib and will address any problems that might happen during the release, making sure that the documentation above is correct.\n\nIn order to ensure that knowledge about releasing Jaeger is spread among maintainers, we rotate the role of Release Manager among maintainers.\n\nHere are the release managers for future versions with the tentative release dates. The release dates are the first Wednesday of the month, and we might skip a release if not enough changes happened since the previous release. In such case, the next tentative release date is the first Wednesday of the subsequent month.\n\n| Version | Release Manager | Tentative release date    |\n|---------|-----------------|---------------------------|\n| 2.17.0  | @albertteoh     | 1 April 2026              |\n| 2.18.0  | @pavolloffay    | 6 May 2026                |\n| 2.19.0  | @joe-elliott    | 3 June 2026               |\n| 2.20.0  | @yurishkuro     | 1 July 2026               |\n| 2.21.0  | @jkowall        | 5 August 2026             |\n| 2.22.0  | @mahadzaryab1   | 2 September 2026          |\n"
  },
  {
    "path": "SECURITY-INSIGHTS.yml",
    "content": "header:\n  schema-version: 1.0.0\n  last-updated: '2026-01-16'\n  last-reviewed: '2026-01-16'\n  expiration-date: '2027-01-16T01:00:00.000Z'\n  project-url: https://github.com/jaegertracing/jaeger/\n  changelog: https://github.com/jaegertracing/jaeger/blob/main/CHANGELOG.md\n  license: https://github.com/jaegertracing/jaeger/blob/main/LICENSE\nproject-lifecycle:\n  bug-fixes-only: false\n  core-maintainers:\n  - https://github.com/jaegertracing/jaeger/blob/main/README.md#maintainers\n  roadmap: https://www.jaegertracing.io/roadmap/\n  release-cycle: https://github.com/jaegertracing/jaeger/blob/main/RELEASE.md#release-managers\n  status: active \ncontribution-policy:\n  accepts-pull-requests: true\n  accepts-automated-pull-requests: true\n  contributing-policy: https://github.com/jaegertracing/jaeger/blob/main/CONTRIBUTING.md \n  code-of-conduct: https://github.com/jaegertracing/jaeger/blob/main/CODE_OF_CONDUCT.md\ndocumentation:\n- https://www.jaegertracing.io/docs/\ndistribution-points:\n- https://github.com/jaegertracing/jaeger/\n- https://hub.docker.com/r/jaegertracing/\n- https://quay.io/organization/jaegertracing/ \nsecurity-artifacts:\n  threat-model:\n    threat-model-created: true\n    evidence-url:\n    - https://github.com/jaegertracing/jaeger/blob/main/THREAT-MODEL.md\n  self-assessment:\n    self-assessment-created: true\n    evidence-url:\n    - https://tag-security.cncf.io/assessments/projects/jaeger/self-assessment/\n    - https://github.com/cncf/tag-security/blob/main/assessments/projects/jaeger/self-assessment.md\nsecurity-testing:\n- tool-type: sca\n  tool-name: Dependabot\n  tool-version: latest\n  integration:\n    ad-hoc: false\n    ci: true\n    before-release: true\n  comment: |\n    Dependabot is enabled for this repo.\nsecurity-contacts:\n- type: website\n  value: https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md\nvulnerability-reporting:\n  accepts-vulnerability-reports: true\n  security-policy: https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md\n  email-contact: jaeger-tracing@googlegroups.com\n  comment: |\n    The first and best way to report a vulnerability is by using private security issues in GitHub or opening an issue on Github. We are also available on the CNCF Slack in the jaeger channel.\ndependencies:\n  third-party-packages: true\n  dependencies-lists:\n  - https://github.com/jaegertracing/jaeger/blob/main/go.mod\n  sbom:\n  - sbom-file: https://github.com/jaegertracing/jaeger/releases/latest/download/jaeger-SBOM.spdx.json \n    sbom-format: SPDX\n    sbom-url: https://github.com/anchore/sbom-action \n  dependencies-lifecycle:\n    policy-url: https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md#security-patch-policy\n  env-dependencies-policy:\n    policy-url: https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md#dependency-policy\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nThe Jaeger project provides community support only for last minor version: bug fixes are released either as part of the next minor version or as an on-demand patch version. Independent of which version is next, all patch versions are cumulative, meaning that they represent the state of our `main` branch at the moment of the release. For instance, if the latest version is 1.19.0, bug fixes are released either as part of 1.20.0 or 1.19.1.\n\nSecurity fixes are given priority and might be enough to cause a new version to be released.\n\n### Security Patch Policy\n\nCVEs in Jaeger code will be patched in the newest Jaeger releases. \n\n### Dependency Policy\n\nDependencies are evaluated before being introduced to ensure they:\n\n1) are actively maintained\n2) are maintained by trustworthy maintainers\n3) are licensed in a way not to impact the Jaeger license based on [the CNCF license allowlist](https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md).\n\nThese evaluations vary from dependency to dependencies.\n\nDependencies are also scheduled for removal if the project has been deprecated or if the project is no longer maintained. Additionally based on license changes we replace dependencies as necessary.\n\nCVEs in dependencies will be patched for all supported versions if the CVE is applicable and is assessed by our\ndependency scanning automation to be of high or critical severity. Automation generates a new dependabot scan daily\nand alerts are addressed.\n\n## Reporting a Vulnerability\n\n_The following is a copy of the [Report a security issue](https://www.jaegertracing.io/report-security-issue/) page from our website. The website's version has precedence in case of conflicts._\n\nIf you find something suspicious and want to report it, we'd really appreciate!\n\n## Ways to report\n\nThe easiest way to report a vulnerability is through the [Security tab on GitHub](https://github.com/jaegertracing/jaeger/security/advisories). This mechanism allows maintainers to communicate privately with you, and you do not need to encrypt your messages.\n\nAlternatively, you can use one of the following public channels to send an **encrypted** message to maintainers:\n\n* Chat room on the [#jaeger channel at the CNCF Slack][slack-room]\n* Send a message to [jaeger-tracing@googlegroups.com][mailing-list]\n* [Open an issue on GitHub](https://github.com/jaegertracing/jaeger/issues)\n\nYou can also submit a fix to the issue by forking the affected repository and sending us a pull request. However, we prefer you'd talk to us first, as our repositories are public and we would like to give a heads-up to our users before disclosing vulnerabilities publicly.\n\n## Our PGP key\n\nIf you choose a public channel to communicate with us, please **encrypt your message** using [our public key](#our-public-key) `ID=C043A4D2B3F2AC31`. It is available in all major key servers and should match the one shown below.\n\nIf you are new to PGP, you can run the following command to encrypt a file called \"message.txt\":\n\n```shell\n# Receive our keys from a key server:\ngpg --keyserver keyserver.ubuntu.com --recv-keys C043A4D2B3F2AC31\n\n# Alternatively, copy the key below to file C043A4D2B3F2AC31.asc and import it:\ngpg --import C043A4D2B3F2AC31.asc\n\n# Encrypt a \"message.txt\" file into \"message.txt.asc\":\ngpg -ea -r C043A4D2B3F2AC31 message.txt\n\n# Send us the resulting \"message.txt.asc\"\n```\n\n### Our public key\n\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQINBFn7N4QBEAC4Vl68Fdcom/U1kb/6zlUeSLh4Vwyr2wvaLd610AUwmrfQC0eh\ne6vRtt//bYr48gHg1wwnbaQgyg+ZvIfjUa6Olhqi3J1itkagy50pQDWk8nfDdbHO\nrgR6W3mFxKgIfAiB07oTY6Gzs8vjuO1VA/5p5DOvvXtTQdgWkI93zqIJhupznDOd\nwPoGF7t6PPTy/hzBJOq9KzX4MgPkOivLAjdSeftzxcvO5oHXEjwAhr5/oaPHvksz\nJ+X8jsBW8J9wSUZWLhJkD5wm1hbcS0MKQAWvM6PpC7RnHmLOJAMBA27ne0qhNzA3\nMRkzWpVUzZc/FvauNk+6ohZMW/HcUlGWsSzt3egih9pFCsz2yhXq1891iswfgYGV\nsRNTDmLNIDk99iDNKZofWDwdOIMJWt0QKSwYbR5mhd7p648RgI+nwyQmX/eX5Eey\nECs56j07ZnUUHAm5n+K53SDnaQo40/bfKEGGJMm+72KLisnOhV6G+3y/Wi7SHEhs\n9hO9Lin7qZ2EBD/KITlCZf4kkIKMc8srkUlfZSDNTVfoP9JHKMqW6Lf5OyskJdG9\nMozUz/Am7QxH8DNZS3UiQubSIX+nuYuc5E03flE9QsFHKqXyYQ49sl8ipLRkV5SI\nc+gTqCeKcwzrNbJ6+zyt/7mQBwP34oV01z2lvgwvVyj3pgzCUFzuJpep0wARAQAB\ntDBKYWVnZXIgVHJhY2luZyA8amFlZ2VyLXRyYWNpbmdAZ29vZ2xlZ3JvdXBzLmNv\nbT6JAk4EEwEIADgWIQT5J7Ll/cozAeoshwfAQ6TSs/KsMQUCWfs3hAIbAwULCQgH\nAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRDAQ6TSs/KsMa25D/9voNhfY8oldKgniMh/\nvzcwiYYM6MFLUanJX2LjNTJ4dXuMYvJtxdfYT/+7PoxyEQfmUj50Ieka+pARyRd4\nr7Rrl8eWLrkURcr72TLz+6tPT1R3r+l0e7p20FEL1w5SNcrBMir3ozwWC9K3U48d\ng0QTD9a3m6oeZ9hqquvsTMfrraVQvx5FdAcfQDSttFuKzfbbacds46I+8Lj4U67O\n4v9I6zscC9MJNth1zy3DyZUGVd5qjkzv5r+LoJfOokC5yj6ErBG8l5HKRtoILWVK\n3IzlFO/jtHiyLJ7wNPSTQjLnhna5fB/eoPiBCGHASZrVwohaaq5dKVJnoy0TW3Sw\nKwshWaNA7zbvFol3DZaFh3tcBNRJwh7rQ4zUEu+uY0M1DzRtnE3NjieZcNNH0wwq\nTbOud0hqpvK9y+xLjsiVhPc1WsTdzafuutFezHILENNDuYaHk1Vwq5FE0wOwWwx9\nah6PDxEgb5P96Zs5FNeT15fiqXKJuyDjLjcML2TUBHBUmhYugVLB9F7TleOwWxL7\n/Ny54so0euqht7agTOS1ySebn5xc2yG96dAOjKJSXt2m5hevHBSVYtF8zWAPciDx\nuEmbjvhgHugDsB9sDu9iQhmgQu7wZ1ihpmcqO725sfW+9aWFHeAf/6dUFoX723Bf\nPF8iTa4onSKnvb55kFGlGAAczbkCDQRZ+zeEARAAvrm8t1j4N4quJ2H3szXyE+Cs\nFsHaVRLK+0IXSLwhgso3ol2cxv8GZjrNdGankpR5wvuseFY1JZ6lQOuamqnsN7yo\nbJxC2g9kUSJcF/cnY+TzIkHxwT492yMgm/FcUrmmWQO0LlcjEpCO8B0UzZU+SqE8\nj0cInOnpSLh77HKBJL62Yu9lBQuSUmEjDMfqt7MtQeyHGSdniNE6kESymnElxch7\nI0l8FHV/IufWzNbvkBszstMS6O4nL8A09HZMsoqeKhvF+A+mXAZ8xEIGls6P6Hrv\nPNt6MFJhva3qtu8WPYY5XCYeA3uD0AC9jjKKF2W6K5GS/iJa8OeG3bj5qbDpv790\nL8JbtlX2ZnV0xXdbzhZsGdwHMWgzu9cmoJLpKtjmhH79KlbyhF8NDtOUw67LKoep\nBdh+lb9htg4EZydfzGxtToD51cais/qqOaaRMTaK/chS7Rr0vIJdCcIztrM7XKRj\nepzyH2upARG9eZ8et2wIvrT94yIQehXUlzllEGCdeeIblBPVP/2XbpBGm2jk7e5s\nxuTQFjkJc64WwwCM4vgdJzMGUXdnyJMr46wqGWAWyaQEHNApDHxR0YwlpL4y35U9\nbHNyJi0JmxVFgl0pBJ6wkSJUJ48Y0WWLUuHNF0MgAzuTAVgcq8EKjbk0P7Z0eH1H\nI56eMxIt14U5uqnw8fEAEQEAAYkCNgQYAQgAIBYhBPknsuX9yjMB6iyHB8BDpNKz\n8qwxBQJZ+zeEAhsMAAoJEMBDpNKz8qwx514QAIbanXq8DEIk0xN64OT8s+5zZspb\n81AV2g8VCur5DI8GQacIQrwfWTqFMt/s11uzMNga6AuhYKENj72Tq0GZHrFPBtD/\nqFsbBl2TaWnmnJcsGHDjtxKJMFG9gZJdXsKl7sCWdxkQW5vxtFLdrYKQ1UdBG24Q\nEHvWaaG1EcNsqb3WNy9h+PYAI/HRS7ntjJdDXNZgb4frJNgZCKCi9tpXS2CvgVpD\nWeRfIFJtbkemJqMsZGMt52HJJ0bMFeaXjyom/NZtgsOCq1J92trR0AzRthjcmY/6\nBevgOrEj8+0aurQ3Qm+IsqPqOyi814yVzOagaZ0dv+rfkomjVWABtoNHkaTyP8h+\ndLh5+GUR2MrpW2TtAXh8QKolUS5x764FYHX7VtgYlZnc+qDfMao8KrD1CHMucwjs\nbysa8gD+jmdegtWFyUvdh+G3EhqW6xldSsixb0enEzzW5utUCvC4xv2tp9GTaUPx\nM3WJzf3w+c4A19AwyYumWf9J4nHFBhNHCq7Mb5I3PRIgrRCQfR9hyaeDMgd6UuSH\nyYdeaxVBmZ20N3D39f7tgfE1oZg1SiHVjmBYtlBu6Jji8wwFjsF1WSDZlmmp/VWJ\n6GzAJggHtgAod6H/lueqcellXEo2usqZLwDqa9SlglhcMWTqysO4j/1vVQpTstwJ\noF+qZY4uEvqFvYo8\n=KQzT\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\n[mailing-list]: https://groups.google.com/forum/#!forum/jaeger-tracing\n[slack-room]: https://cloud-native.slack.com/archives/CGG7NFUJ3\n\n## Automated Security Scanners\n\nWe do not accept raw, unanalyzed reports from automated security scanners. Most scanners are generic and produce a high volume of false positives, especially for Go and NPM dependencies.\n\nIf you use a security scanner:\n\n1) Only report findings that are actually applicable to Jaeger, with an explanation of why.\n2) Verify Go dependency CVEs with [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) run against the source code (not a binary). If the vulnerable code path is not reachable from Jaeger, it is not actionable.\n3) Check if we already track it. Jaeger uses Dependabot for automated dependency scanning (see [Dependency Policy](#dependency-policy)). Most dependency CVEs are already tracked.\n4) Use the proper reporting process described in [Reporting a Vulnerability](#reporting-a-vulnerability).\n\n\n## Security Documentation\n\nFor more detailed security information, see:\n\n| Document | Description |\n|----------|-------------|\n| [Threat Model](docs/security/threat-model.md) | Threat model and trust boundaries |\n| [Assurance Case](docs/security/assurance-case.md) | Security assurance case |\n| [Security Architecture](docs/security/architecture.md) | TLS and cryptographic practices |\n| [Self-Assessment](docs/security/self-assessment.md) | CNCF TAG Security self-assessment |\n| [Release Verification](docs/security/verifying-releases.md) | Release signature verification |\n"
  },
  {
    "path": "THREAT-MODEL.md",
    "content": "# Jaeger Threat Model\n\nThis is a placeholder for the Jaeger Threat Model. This will be based on [OSSF standards](https://github.com/ossf/security-insights-spec/tree/main/docs/threat-model) and examples of existing threat models. This is a significant chunk of work for Jaeger due to the diversity and complexity of all the supported components in deployment. "
  },
  {
    "path": "_To_People_of_Russia.md",
    "content": "## 🇷🇺 Русским гражданам\n\nВ Украине сейчас идет война. Силами РФ наносятся удары по гражданской инфраструктуре в [Харькове][1], [Киеве][2], [Чернигове][3], [Сумах][4], [Ирпене][5] и десятках других городов. Гибнут люди - и гражданское население, и военные, в том числе российские призывники, которых бросили воевать. Чтобы лишить собственный народ доступа к информации, правительство РФ запретило называть войну войной, закрыло независимые СМИ и принимает сейчас ряд диктаторских законов. Эти законы призваны заткнуть рот всем, кто против войны. За обычный призыв к миру сейчас можно получить несколько лет тюрьмы.\n\nНе молчите! Молчание - знак вашего согласия с политикой российского правительства.\n**Вы можете сделать выбор НЕ МОЛЧАТЬ.**\n\n---\n\n## 🇺🇸 To people of Russia\n\nThere is a war in Ukraine right now. The forces of the Russian Federation are attacking civilian infrastructure in [Kharkiv][1], [Kyiv][2], [Chernihiv][3], [Sumy][4], [Irpin][5] and dozens of other cities. People are dying – both civilians and military servicemen, including Russian conscripts who were thrown into the fighting. In order to deprive its own people of access to information, the government of the Russian Federation has forbidden calling a war a war, shut down independent media and is passing a number of dictatorial laws. These laws are meant to silence all those who are against war. You can be jailed for multiple years for simply calling for peace.\n\nDo not be silent! Silence is a sign that you accept the Russian government's policy.\n**You can choose NOT TO BE SILENT.**\n\n[1]: <https://cloudfront-us-east-2.images.arcpublishing.com/reuters/P7K2MSZDGFMIJPDD7CI2GIROJI.jpg> \"Kharkiv under attack\"\n[2]: <https://gdb.voanews.com/01bd0000-0aff-0242-fad0-08d9fc92c5b3_cx0_cy5_cw0_w1023_r1_s.jpg> \"Kyiv under attack\"\n[3]: <https://ichef.bbci.co.uk/news/976/cpsprodpb/163DD/production/_123510119_hi074310744.jpg> \"Chernihiv under attack\"\n[4]: <https://www.youtube.com/watch?v=8K-bkqKKf2A> \"Sumy under attack\"\n[5]: <https://cloudfront-us-east-2.images.arcpublishing.com/reuters/K4MTMLEHTRKGFK3GSKAT4GR3NE.jpg> \"Irpin under attack\"\n"
  },
  {
    "path": "cmd/anonymizer/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nFROM scratch\nARG TARGETARCH\nARG USER_UID=10001\n\nCOPY anonymizer-linux-$TARGETARCH /go/bin/anonymizer-linux\nENTRYPOINT [\"/go/bin/anonymizer-linux\"]\nUSER ${USER_UID}\n"
  },
  {
    "path": "cmd/anonymizer/app/anonymizer/anonymizer.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage anonymizer\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/uimodel\"\n\tuiconv \"github.com/jaegertracing/jaeger/internal/uimodel/converter/v1/json\"\n)\n\nvar allowedTags = map[string]bool{\n\t\"error\":               true,\n\t\"http.method\":         true,\n\t\"http.status_code\":    true,\n\tmodel.SpanKindKey:     true,\n\tmodel.SamplerTypeKey:  true,\n\tmodel.SamplerParamKey: true,\n}\n\nconst PermUserRW = 0o600 // Read-write for owner only\n\n// mapping stores the mapping of service/operation names to their one-way hashes,\n// so that we can do a reverse lookup should the researchers have questions.\ntype mapping struct {\n\tServices   map[string]string\n\tOperations map[string]string // key=[service]:operation\n}\n\n// Anonymizer transforms Jaeger span in the domain model by obfuscating site-specific strings,\n// like service and operation names, and removes custom tags. It returns obfuscated span in the\n// Jaeger UI format, to make it easy to visualize traces.\n//\n// The mapping from original to obfuscated strings is stored in a file and can be reused between runs.\ntype Anonymizer struct {\n\tmappingFile string\n\tlogger      *zap.Logger\n\tlock        sync.Mutex\n\tmapping     mapping\n\toptions     Options\n\tcancel      context.CancelFunc\n\twg          sync.WaitGroup\n}\n\n// Options represents the various options with which the anonymizer can be configured.\ntype Options struct {\n\tHashStandardTags bool `yaml:\"hash_standard_tags\" name:\"hash_standard_tags\"`\n\tHashCustomTags   bool `yaml:\"hash_custom_tags\" name:\"hash_custom_tags\"`\n\tHashLogs         bool `yaml:\"hash_logs\" name:\"hash_logs\"`\n\tHashProcess      bool `yaml:\"hash_process\" name:\"hash_process\"`\n}\n\n// New creates new Anonymizer. The mappingFile stores the mapping from original to\n// obfuscated strings, in case later investigations require looking at the original traces.\nfunc New(mappingFile string, options Options, logger *zap.Logger) *Anonymizer {\n\tctx, cancel := context.WithCancel(context.Background())\n\ta := &Anonymizer{\n\t\tmappingFile: mappingFile,\n\t\tlogger:      logger,\n\t\tmapping: mapping{\n\t\t\tServices:   make(map[string]string),\n\t\t\tOperations: make(map[string]string),\n\t\t},\n\t\toptions: options,\n\t\tcancel:  cancel,\n\t}\n\tif _, err := os.Stat(filepath.Clean(mappingFile)); err == nil { //nolint:gosec // G703 - CLI tool, path from command-line args\n\t\tdat, err := os.ReadFile(filepath.Clean(mappingFile)) //nolint:gosec // G703\n\t\tif err != nil {\n\t\t\tlogger.Fatal(\"Cannot load previous mapping\", zap.Error(err))\n\t\t}\n\t\tif err := json.Unmarshal(dat, &a.mapping); err != nil {\n\t\t\tlogger.Fatal(\"Cannot unmarshal previous mapping\", zap.Error(err))\n\t\t}\n\t}\n\ta.wg.Go(func() {\n\t\tticker := time.NewTicker(10 * time.Second)\n\t\tdefer ticker.Stop()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\ta.SaveMapping()\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n\treturn a\n}\n\nfunc (a *Anonymizer) Stop() {\n\ta.cancel()\n\ta.wg.Wait()\n}\n\n// SaveMapping writes the mapping from original to obfuscated strings to a file.\n// It is called by the anonymizer itself periodically, and should be called at\n// the end of the extraction run.\nfunc (a *Anonymizer) SaveMapping() {\n\ta.lock.Lock()\n\tdefer a.lock.Unlock()\n\tdat, err := json.Marshal(a.mapping)\n\tif err != nil {\n\t\ta.logger.Error(\"Failed to marshal mapping file\", zap.Error(err))\n\t\treturn\n\t}\n\tif err := os.WriteFile(filepath.Clean(a.mappingFile), dat, PermUserRW); err != nil {\n\t\ta.logger.Error(\"Failed to write mapping file\", zap.Error(err))\n\t\treturn\n\t}\n\ta.logger.Sugar().Infof(\"Saved mapping file %s: %s\", a.mappingFile, string(dat))\n}\n\nfunc (a *Anonymizer) mapServiceName(service string) string {\n\treturn a.mapString(service, a.mapping.Services)\n}\n\nfunc (a *Anonymizer) mapOperationName(service, operation string) string {\n\tv := fmt.Sprintf(\"[%s]:%s\", service, operation)\n\treturn a.mapString(v, a.mapping.Operations)\n}\n\nfunc (a *Anonymizer) mapString(v string, m map[string]string) string {\n\ta.lock.Lock()\n\tdefer a.lock.Unlock()\n\tif s, ok := m[v]; ok {\n\t\treturn s\n\t}\n\ts := hash(v)\n\tm[v] = s\n\treturn s\n}\n\nfunc hash(value string) string {\n\th := fnv.New64()\n\t_, _ = h.Write([]byte(value))\n\treturn fmt.Sprintf(\"%016x\", h.Sum64())\n}\n\n// AnonymizeSpan obfuscates and converts the span.\nfunc (a *Anonymizer) AnonymizeSpan(span *model.Span) *uimodel.Span {\n\tservice := span.Process.ServiceName\n\tspan.OperationName = a.mapOperationName(service, span.OperationName)\n\n\toutputTags := filterStandardTags(span.Tags)\n\t// when true, the allowedTags are hashed and when false they are preserved as it is\n\tif a.options.HashStandardTags {\n\t\toutputTags = hashTags(outputTags)\n\t}\n\t// when true, all tags other than allowedTags are hashed, when false they are dropped\n\tif a.options.HashCustomTags {\n\t\tcustomTags := hashTags(filterCustomTags(span.Tags))\n\t\toutputTags = append(outputTags, customTags...)\n\t}\n\tspan.Tags = outputTags\n\n\t// when true, logs are hashed, when false, they are dropped\n\tif a.options.HashLogs {\n\t\tfor _, log := range span.Logs {\n\t\t\tlog.Fields = hashTags(log.Fields)\n\t\t}\n\t} else {\n\t\tspan.Logs = nil\n\t}\n\n\tspan.Process.ServiceName = a.mapServiceName(service)\n\n\t// when true, process tags are hashed, when false they are dropped\n\tif a.options.HashProcess {\n\t\tspan.Process.Tags = hashTags(span.Process.Tags)\n\t} else {\n\t\tspan.Process.Tags = nil\n\t}\n\n\tspan.Warnings = nil\n\treturn uiconv.FromDomainEmbedProcess(span)\n}\n\n// filterStandardTags returns only allowedTags\nfunc filterStandardTags(tags []model.KeyValue) []model.KeyValue {\n\tout := make([]model.KeyValue, 0, len(tags))\n\tfor _, tag := range tags {\n\t\tif !allowedTags[tag.Key] {\n\t\t\tcontinue\n\t\t}\n\t\tif tag.Key == \"error\" {\n\t\t\tswitch tag.VType {\n\t\t\tcase model.BoolType:\n\t\t\t\t// allowed\n\t\t\tcase model.StringType:\n\t\t\t\tif tag.VStr != \"true\" && tag.VStr != \"false\" {\n\t\t\t\t\ttag = model.Bool(\"error\", true)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\ttag = model.Bool(\"error\", true)\n\t\t\t}\n\t\t}\n\t\tout = append(out, tag)\n\t}\n\treturn out\n}\n\n// filterCustomTags returns all tags other than allowedTags\nfunc filterCustomTags(tags []model.KeyValue) []model.KeyValue {\n\tout := make([]model.KeyValue, 0, len(tags))\n\tfor _, tag := range tags {\n\t\tif !allowedTags[tag.Key] {\n\t\t\tout = append(out, tag)\n\t\t}\n\t}\n\treturn out\n}\n\n// hashTags converts each tag into corresponding string values\n// and then find its hash\nfunc hashTags(tags []model.KeyValue) []model.KeyValue {\n\tout := make([]model.KeyValue, 0, len(tags))\n\tfor _, tag := range tags {\n\t\tkv := model.String(hash(tag.Key), hash(tag.AsString()))\n\t\tout = append(out, kv)\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/anonymizer/anonymizer_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage anonymizer\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nvar tags = []model.KeyValue{\n\tmodel.Bool(\"error\", true),\n\tmodel.String(\"http.method\", http.MethodPost),\n\tmodel.Bool(\"foobar\", true),\n}\n\nvar traceID = model.NewTraceID(1, 2)\n\nvar span1 = &model.Span{\n\tTraceID: traceID,\n\tSpanID:  model.NewSpanID(1),\n\tProcess: &model.Process{\n\t\tServiceName: \"serviceName\",\n\t\tTags:        tags,\n\t},\n\tOperationName: \"operationName\",\n\tTags:          tags,\n\tLogs: []model.Log{\n\t\t{\n\t\t\tTimestamp: time.Now(),\n\t\t\tFields: []model.KeyValue{\n\t\t\t\tmodel.String(\"logKey\", \"logValue\"),\n\t\t\t},\n\t\t},\n\t},\n\tDuration:  time.Second * 5,\n\tStartTime: time.Unix(300, 0),\n}\n\nvar span2 = &model.Span{\n\tTraceID: traceID,\n\tSpanID:  model.NewSpanID(1),\n\tProcess: &model.Process{\n\t\tServiceName: \"serviceName\",\n\t\tTags:        tags,\n\t},\n\tOperationName: \"operationName\",\n\tTags:          tags,\n\tLogs: []model.Log{\n\t\t{\n\t\t\tTimestamp: time.Now(),\n\t\t\tFields: []model.KeyValue{\n\t\t\t\tmodel.String(\"logKey\", \"logValue\"),\n\t\t\t},\n\t\t},\n\t},\n\tDuration:  time.Second * 5,\n\tStartTime: time.Unix(300, 0),\n}\n\nfunc TestNew(t *testing.T) {\n\tnopLogger := zap.NewNop()\n\ttempDir := t.TempDir()\n\n\tfile, err := os.CreateTemp(tempDir, \"mapping.json\")\n\trequire.NoError(t, err)\n\tdefer file.Close()\n\n\t_, err = file.WriteString(`\n{\n    \"services\": {\n\t\t\"api\": \"hashed_api\"\n\t},\n\t\"operations\": {\n\t\t\"[api]:delete\": \"hashed_api_delete\"\n\t}\n}\n`)\n\trequire.NoError(t, err)\n\n\tanonymizer := New(file.Name(), Options{}, nopLogger)\n\tdefer anonymizer.Stop()\n\tassert.NotNil(t, anonymizer)\n}\n\nfunc TestAnonymizer_SaveMapping(t *testing.T) {\n\tnopLogger := zap.NewNop()\n\tmapping := mapping{\n\t\tServices:   make(map[string]string),\n\t\tOperations: make(map[string]string),\n\t}\n\n\ttests := []struct {\n\t\tname        string\n\t\tmappingFile string\n\t}{\n\t\t{\n\t\t\tname:        \"fail to write mapping file\",\n\t\t\tmappingFile: \"\",\n\t\t},\n\t\t{\n\t\t\tname:        \"save mapping file successfully\",\n\t\t\tmappingFile: filepath.Join(t.TempDir(), \"mapping.json\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(_ *testing.T) {\n\t\t\tanonymizer := Anonymizer{\n\t\t\t\tlogger:      nopLogger,\n\t\t\t\tmapping:     mapping,\n\t\t\t\tmappingFile: tt.mappingFile,\n\t\t\t}\n\t\t\tanonymizer.SaveMapping()\n\t\t})\n\t}\n}\n\nfunc TestAnonymizer_FilterStandardTags(t *testing.T) {\n\texpected := []model.KeyValue{\n\t\tmodel.Bool(\"error\", true),\n\t\tmodel.String(\"http.method\", http.MethodPost),\n\t}\n\tactual := filterStandardTags(tags)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestAnonymizer_FilterCustomTags(t *testing.T) {\n\texpected := []model.KeyValue{\n\t\tmodel.Bool(\"foobar\", true),\n\t}\n\tactual := filterCustomTags(tags)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestAnonymizer_Hash(t *testing.T) {\n\tdata := \"foobar\"\n\texpected := \"340d8765a4dda9c2\"\n\tactual := hash(data)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestAnonymizer_AnonymizeSpan_AllTrue(t *testing.T) {\n\tanonymizer := &Anonymizer{\n\t\tmapping: mapping{\n\t\t\tServices:   make(map[string]string),\n\t\t\tOperations: make(map[string]string),\n\t\t},\n\t\toptions: Options{\n\t\t\tHashStandardTags: true,\n\t\t\tHashCustomTags:   true,\n\t\t\tHashProcess:      true,\n\t\t\tHashLogs:         true,\n\t\t},\n\t}\n\t_ = anonymizer.AnonymizeSpan(span1)\n\tassert.Len(t, span1.Tags, 3)\n\tassert.Len(t, span1.Logs, 1)\n\tassert.Len(t, span1.Process.Tags, 3)\n}\n\nfunc TestAnonymizer_AnonymizeSpan_AllFalse(t *testing.T) {\n\tanonymizer := &Anonymizer{\n\t\tmapping: mapping{\n\t\t\tServices:   make(map[string]string),\n\t\t\tOperations: make(map[string]string),\n\t\t},\n\t\toptions: Options{\n\t\t\tHashStandardTags: false,\n\t\t\tHashCustomTags:   false,\n\t\t\tHashProcess:      false,\n\t\t\tHashLogs:         false,\n\t\t},\n\t}\n\t_ = anonymizer.AnonymizeSpan(span2)\n\tassert.Len(t, span2.Tags, 2)\n\tassert.Empty(t, span2.Logs)\n\tassert.Empty(t, span2.Process.Tags)\n}\n\nfunc TestAnonymizer_MapString_Present(t *testing.T) {\n\tv := \"foobar\"\n\tm := map[string]string{\n\t\t\"foobar\": \"hashed_foobar\",\n\t}\n\tanonymizer := &Anonymizer{}\n\tactual := anonymizer.mapString(v, m)\n\tassert.Equal(t, \"hashed_foobar\", actual)\n}\n\nfunc TestAnonymizer_MapString_Absent(t *testing.T) {\n\tv := \"foobar\"\n\tm := map[string]string{}\n\tanonymizer := &Anonymizer{}\n\tactual := anonymizer.mapString(v, m)\n\tassert.Equal(t, \"340d8765a4dda9c2\", actual)\n}\n\nfunc TestAnonymizer_MapServiceName(t *testing.T) {\n\tanonymizer := &Anonymizer{\n\t\tmapping: mapping{\n\t\t\tServices: map[string]string{\n\t\t\t\t\"api\": \"hashed_api\",\n\t\t\t},\n\t\t},\n\t}\n\tactual := anonymizer.mapServiceName(\"api\")\n\tassert.Equal(t, \"hashed_api\", actual)\n}\n\nfunc TestAnonymizer_MapOperationName(t *testing.T) {\n\tanonymizer := &Anonymizer{\n\t\tmapping: mapping{\n\t\t\tServices: map[string]string{\n\t\t\t\t\"api\": \"hashed_api\",\n\t\t\t},\n\t\t\tOperations: map[string]string{\n\t\t\t\t\"[api]:delete\": \"hashed_api_delete\",\n\t\t\t},\n\t\t},\n\t}\n\tactual := anonymizer.mapOperationName(\"api\", \"delete\")\n\tassert.Equal(t, \"hashed_api_delete\", actual)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/anonymizer/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage anonymizer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/flags.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\n// Options represent configurable parameters for jaeger-anonymizer\ntype Options struct {\n\tQueryGRPCHostPort string\n\tMaxSpansCount     int\n\tTraceID           string\n\tOutputDir         string\n\tHashStandardTags  bool\n\tHashCustomTags    bool\n\tHashLogs          bool\n\tHashProcess       bool\n\tStartTime         int64\n\tEndTime           int64\n}\n\nconst (\n\tqueryGRPCHostPortFlag = \"query-host-port\"\n\toutputDirFlag         = \"output-dir\"\n\ttraceIDFlag           = \"trace-id\"\n\thashStandardTagsFlag  = \"hash-standard-tags\"\n\thashCustomTagsFlag    = \"hash-custom-tags\"\n\thashLogsFlag          = \"hash-logs\"\n\thashProcessFlag       = \"hash-process\"\n\tmaxSpansCount         = \"max-spans-count\"\n\tstartTime             = \"start-time\"\n\tendTime               = \"end-time\"\n)\n\n// AddFlags adds flags for anonymizer main program\nfunc (o *Options) AddFlags(command *cobra.Command) {\n\tcommand.Flags().StringVar(\n\t\t&o.QueryGRPCHostPort,\n\t\tqueryGRPCHostPortFlag,\n\t\t\"localhost:16686\",\n\t\t\"The host:port of the jaeger-query endpoint\")\n\tcommand.Flags().StringVar(\n\t\t&o.OutputDir,\n\t\toutputDirFlag,\n\t\t\"/tmp\",\n\t\t\"The directory to store the anonymized trace\")\n\tcommand.Flags().StringVar(\n\t\t&o.TraceID,\n\t\ttraceIDFlag,\n\t\t\"\",\n\t\t\"The trace-id of trace to anonymize\")\n\tcommand.Flags().BoolVar(\n\t\t&o.HashStandardTags,\n\t\thashStandardTagsFlag,\n\t\tfalse,\n\t\t\"Whether to hash standard tags\")\n\tcommand.Flags().BoolVar(\n\t\t&o.HashCustomTags,\n\t\thashCustomTagsFlag,\n\t\tfalse,\n\t\t\"Whether to hash custom tags\")\n\tcommand.Flags().BoolVar(\n\t\t&o.HashLogs,\n\t\thashLogsFlag,\n\t\tfalse,\n\t\t\"Whether to hash logs\")\n\tcommand.Flags().BoolVar(\n\t\t&o.HashProcess,\n\t\thashProcessFlag,\n\t\tfalse,\n\t\t\"Whether to hash process\")\n\tcommand.Flags().IntVar(\n\t\t&o.MaxSpansCount,\n\t\tmaxSpansCount,\n\t\t-1,\n\t\t\"The maximum number of spans to anonymize\")\n\tcommand.Flags().Int64Var(\n\t\t&o.StartTime,\n\t\tstartTime,\n\t\t0,\n\t\t\"The start time of time window for searching trace, timestampe in unix nanoseconds\")\n\tcommand.Flags().Int64Var(\n\t\t&o.EndTime,\n\t\tendTime,\n\t\t0,\n\t\t\"The end time of time window for searching trace, timestampe in unix nanoseconds\")\n\n\t// mark traceid flag as mandatory\n\tcommand.MarkFlagRequired(traceIDFlag)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/flags_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestOptionsWithDefaultFlags(t *testing.T) {\n\to := Options{}\n\tc := cobra.Command{}\n\to.AddFlags(&c)\n\n\tassert.Equal(t, \"localhost:16686\", o.QueryGRPCHostPort)\n\tassert.Equal(t, \"/tmp\", o.OutputDir)\n\tassert.False(t, o.HashStandardTags)\n\tassert.False(t, o.HashCustomTags)\n\tassert.False(t, o.HashLogs)\n\tassert.False(t, o.HashProcess)\n\tassert.Equal(t, -1, o.MaxSpansCount)\n\tassert.Equal(t, int64(0), o.StartTime)\n\tassert.Equal(t, int64(0), o.EndTime)\n}\n\nfunc TestOptionsWithFlags(t *testing.T) {\n\to := Options{}\n\tc := cobra.Command{}\n\n\to.AddFlags(&c)\n\tc.ParseFlags([]string{\n\t\t\"--query-host-port=192.168.1.10:16686\",\n\t\t\"--output-dir=/data\",\n\t\t\"--trace-id=6ef2debb698f2f7c\",\n\t\t\"--hash-standard-tags\",\n\t\t\"--hash-custom-tags\",\n\t\t\"--hash-logs\",\n\t\t\"--hash-process\",\n\t\t\"--max-spans-count=100\",\n\t\t\"--start-time=1\",\n\t\t\"--end-time=2\",\n\t})\n\n\tassert.Equal(t, \"192.168.1.10:16686\", o.QueryGRPCHostPort)\n\tassert.Equal(t, \"/data\", o.OutputDir)\n\tassert.Equal(t, \"6ef2debb698f2f7c\", o.TraceID)\n\tassert.True(t, o.HashStandardTags)\n\tassert.True(t, o.HashCustomTags)\n\tassert.True(t, o.HashLogs)\n\tassert.True(t, o.HashProcess)\n\tassert.Equal(t, 100, o.MaxSpansCount)\n\tassert.Equal(t, int64(1), o.StartTime)\n\tassert.Equal(t, int64(2), o.EndTime)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/query/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage query\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/query/query.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage query\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// Query represents a jaeger-query's query for trace-id\ntype Query struct {\n\tclient api_v2.QueryServiceClient\n\tconn   *grpc.ClientConn\n}\n\n// New creates a Query object\nfunc New(addr string) (*Query, error) {\n\tconn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to connect with the jaeger-query service: %w\", err)\n\t}\n\n\treturn &Query{\n\t\tclient: api_v2.NewQueryServiceClient(conn),\n\t\tconn:   conn,\n\t}, nil\n}\n\n// unwrapNotFoundErr is a conversion function\nfunc unwrapNotFoundErr(err error) error {\n\tif s, _ := status.FromError(err); s != nil {\n\t\tif strings.Contains(s.Message(), spanstore.ErrTraceNotFound.Error()) {\n\t\t\treturn spanstore.ErrTraceNotFound\n\t\t}\n\t}\n\treturn err\n}\n\n// QueryTrace queries for a trace and returns all spans inside it\nfunc (q *Query) QueryTrace(traceID string, startTime time.Time, endTime time.Time) ([]model.Span, error) {\n\tmTraceID, err := model.TraceIDFromString(traceID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert the provided trace id: %w\", err)\n\t}\n\n\trequest := api_v2.GetTraceRequest{\n\t\tTraceID:   mTraceID,\n\t\tStartTime: startTime,\n\t\tEndTime:   endTime,\n\t}\n\n\tstream, err := q.client.GetTrace(context.Background(), &request)\n\tif err != nil {\n\t\treturn nil, unwrapNotFoundErr(err)\n\t}\n\n\tvar spans []model.Span\n\tfor received, err := stream.Recv(); !errors.Is(err, io.EOF); received, err = stream.Recv() {\n\t\tif err != nil {\n\t\t\treturn nil, unwrapNotFoundErr(err)\n\t\t}\n\t\tspans = append(spans, received.Spans...)\n\t}\n\n\treturn spans, nil\n}\n\n// Close closes the grpc client connection\nfunc (q *Query) Close() error {\n\treturn q.conn.Close()\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/query/query_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage query\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t_ \"github.com/jaegertracing/jaeger/internal/gogocodec\" // force gogo codec registration\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\nvar (\n\tmockInvalidTraceID = \"xyz\"\n\tmockTraceID        = model.NewTraceID(0, 123456)\n\n\tmockTraceGRPC = &model.Trace{\n\t\tSpans: []*model.Span{\n\t\t\t{\n\t\t\t\tTraceID: mockTraceID,\n\t\t\t\tSpanID:  model.NewSpanID(1),\n\t\t\t\tProcess: &model.Process{},\n\t\t\t},\n\t\t\t{\n\t\t\t\tTraceID: mockTraceID,\n\t\t\t\tSpanID:  model.NewSpanID(2),\n\t\t\t\tProcess: &model.Process{},\n\t\t\t},\n\t\t},\n\t\tWarnings: []string{},\n\t}\n)\n\nvar errUninitializedTraceID = status.Error(codes.InvalidArgument, \"uninitialized TraceID is not allowed\")\n\n// testGRPCHandler is a minimal implementation of api_v2.QueryServiceServer\n// for testing purposes. It only implements GetTrace, using the embedded\n// UnimplementedQueryServiceServer for other methods.\ntype testGRPCHandler struct {\n\tapi_v2.UnimplementedQueryServiceServer\n\treturnTrace    *model.Trace\n\treturnError    error\n\tfailDuringRecv bool\n}\n\n// GetTrace implements the gRPC GetTrace method by returning test data directly.\nfunc (g *testGRPCHandler) GetTrace(r *api_v2.GetTraceRequest, stream api_v2.QueryService_GetTraceServer) error {\n\tif r.TraceID == (model.TraceID{}) {\n\t\treturn errUninitializedTraceID\n\t}\n\tif g.returnError != nil {\n\t\tif errors.Is(g.returnError, spanstore.ErrTraceNotFound) {\n\t\t\treturn status.Errorf(codes.NotFound, \"trace not found: %v\", g.returnError)\n\t\t}\n\t\treturn status.Errorf(codes.Internal, \"failed to fetch spans from the backend: %v\", g.returnError)\n\t}\n\tif g.returnTrace == nil {\n\t\treturn status.Errorf(codes.NotFound, \"trace not found\")\n\t}\n\tif g.failDuringRecv {\n\t\t// Send first chunk then fail\n\t\tchunk := &api_v2.SpansResponseChunk{Spans: []model.Span{*g.returnTrace.Spans[0]}}\n\t\tif err := stream.Send(chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn status.Errorf(codes.Internal, \"failed during recv\")\n\t}\n\treturn g.sendSpanChunks(g.returnTrace.Spans, stream.Send)\n}\n\n// sendSpanChunks sends spans in chunks to the client.\nfunc (*testGRPCHandler) sendSpanChunks(spans []*model.Span, sendFn func(*api_v2.SpansResponseChunk) error) error {\n\tchunk := make([]model.Span, 0, len(spans))\n\tfor _, span := range spans {\n\t\tchunk = append(chunk, *span)\n\t}\n\treturn sendFn(&api_v2.SpansResponseChunk{Spans: chunk})\n}\n\ntype mockQueryClient struct {\n\tapi_v2.QueryServiceClient\n\tgetTraceErr error\n}\n\nfunc (m *mockQueryClient) GetTrace(ctx context.Context, in *api_v2.GetTraceRequest, opts ...grpc.CallOption) (api_v2.QueryService_GetTraceClient, error) {\n\tif m.getTraceErr != nil {\n\t\treturn nil, m.getTraceErr\n\t}\n\treturn m.QueryServiceClient.GetTrace(ctx, in, opts...)\n}\n\ntype testServer struct {\n\taddress net.Addr\n\tserver  *grpc.Server\n\thandler *testGRPCHandler\n}\n\nfunc newTestServer(t *testing.T) *testServer {\n\th := &testGRPCHandler{}\n\n\tserver := grpc.NewServer()\n\tapi_v2.RegisterQueryServiceServer(server, h)\n\n\tlis, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err)\n\n\tvar started, exited sync.WaitGroup\n\tstarted.Add(1)\n\texited.Go(func() {\n\t\tstarted.Done()\n\t\tassert.NoError(t, server.Serve(lis))\n\t})\n\tstarted.Wait()\n\tt.Cleanup(func() {\n\t\tserver.Stop()\n\t\texited.Wait() // don't allow test to finish before server exits\n\t})\n\n\treturn &testServer{\n\t\tserver:  server,\n\t\taddress: lis.Addr(),\n\t\thandler: h,\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\tserver := newTestServer(t)\n\n\tquery, err := New(server.address.String())\n\trequire.NoError(t, err)\n\tdefer query.Close()\n\n\tassert.NotNil(t, query)\n\n\tt.Run(\"invalid address\", func(t *testing.T) {\n\t\t// Try a definitively invalid URI to trigger parser error in NewClient.\n\t\tq, err := New(\"invalid-scheme://%%\")\n\t\tif err != nil {\n\t\t\tassert.Nil(t, q)\n\t\t} else if q != nil {\n\t\t\tq.Close()\n\t\t}\n\t})\n}\n\nfunc TestClose(t *testing.T) {\n\ts := newTestServer(t)\n\tq, err := New(s.address.String())\n\trequire.NoError(t, err)\n\tassert.NoError(t, q.Close())\n}\n\nfunc TestQueryTrace(t *testing.T) {\n\ts := newTestServer(t)\n\tq, err := New(s.address.String())\n\trequire.NoError(t, err)\n\tdefer q.Close()\n\n\tt.Run(\"No error\", func(t *testing.T) {\n\t\tstartTime := time.Date(1970, time.January, 1, 0, 0, 0, 1000, time.UTC)\n\t\tendTime := time.Date(1970, time.January, 1, 0, 0, 0, 2000, time.UTC)\n\t\ts.handler.returnTrace = mockTraceGRPC\n\t\ts.handler.returnError = nil\n\n\t\tspans, err := q.QueryTrace(mockTraceID.String(), startTime, endTime)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, spans, len(mockTraceGRPC.Spans))\n\t})\n\n\tt.Run(\"Invalid TraceID\", func(t *testing.T) {\n\t\t_, err := q.QueryTrace(mockInvalidTraceID, time.Time{}, time.Time{})\n\t\tassert.ErrorContains(t, err, \"failed to convert the provided trace id\")\n\t})\n\n\tt.Run(\"General error from GetTrace\", func(t *testing.T) {\n\t\ts.handler.returnTrace = nil\n\t\ts.handler.returnError = errors.New(\"random error\")\n\n\t\tspans, err := q.QueryTrace(mockTraceID.String(), time.Time{}, time.Time{})\n\t\tassert.Nil(t, spans)\n\t\tassert.ErrorContains(t, err, \"random error\")\n\t})\n\n\tt.Run(\"Trace not found\", func(t *testing.T) {\n\t\ts.handler.returnTrace = nil\n\t\ts.handler.returnError = spanstore.ErrTraceNotFound\n\n\t\tspans, err := q.QueryTrace(mockTraceID.String(), time.Time{}, time.Time{})\n\t\tassert.Nil(t, spans)\n\t\tassert.ErrorIs(t, err, spanstore.ErrTraceNotFound)\n\t})\n\n\tt.Run(\"Error from GetTrace (immediate)\", func(t *testing.T) {\n\t\toriginalClient := q.client\n\t\tmockClient := &mockQueryClient{\n\t\t\tQueryServiceClient: q.client,\n\t\t\tgetTraceErr:        errors.New(\"immediate error\"),\n\t\t}\n\t\tq.client = mockClient\n\t\tdefer func() { q.client = originalClient }()\n\n\t\tspans, err := q.QueryTrace(mockTraceID.String(), time.Time{}, time.Time{})\n\t\tassert.Nil(t, spans)\n\t\tassert.ErrorContains(t, err, \"immediate error\")\n\t})\n\n\tt.Run(\"Error from stream.Recv\", func(t *testing.T) {\n\t\ts.handler.returnTrace = mockTraceGRPC\n\t\ts.handler.returnError = nil\n\t\ts.handler.failDuringRecv = true\n\t\tdefer func() { s.handler.failDuringRecv = false }()\n\n\t\tspans, err := q.QueryTrace(mockTraceID.String(), time.Time{}, time.Time{})\n\t\tassert.Nil(t, spans)\n\t\tassert.ErrorContains(t, err, \"failed during recv\")\n\t})\n}\n\nfunc TestUnwrapNotFoundErr(t *testing.T) {\n\tt.Run(\"non-gRPC error\", func(t *testing.T) {\n\t\terr := errors.New(\"standard error\")\n\t\tassert.Equal(t, err, unwrapNotFoundErr(err))\n\t})\n\n\tt.Run(\"gRPC error with trace not found\", func(t *testing.T) {\n\t\terr := status.Error(codes.NotFound, \"trace not found\")\n\t\tassert.Equal(t, spanstore.ErrTraceNotFound, unwrapNotFoundErr(err))\n\t})\n\n\tt.Run(\"gRPC error without trace not found\", func(t *testing.T) {\n\t\terr := status.Error(codes.Internal, \"internal error\")\n\t\tassert.Equal(t, err, unwrapNotFoundErr(err))\n\t})\n\n\tt.Run(\"nil error\", func(t *testing.T) {\n\t\tassert.NoError(t, unwrapNotFoundErr(nil))\n\t})\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/extractor.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/uimodel\"\n)\n\n// extractor reads the spans from reader, filters by traceID, and stores as JSON into uiFile.\ntype extractor struct {\n\tuiFile  *os.File\n\ttraceID string\n\treader  *spanReader\n\tlogger  *zap.Logger\n}\n\n// newExtractor creates extractor.\nfunc newExtractor(uiFile string, traceID string, reader *spanReader, logger *zap.Logger) (*extractor, error) {\n\tf, err := os.OpenFile(uiFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot create output file: %w\", err)\n\t}\n\tlogger.Sugar().Infof(\"Writing spans to UI file %s\", uiFile)\n\n\treturn &extractor{\n\t\tuiFile:  f,\n\t\ttraceID: traceID,\n\t\treader:  reader,\n\t\tlogger:  logger,\n\t}, nil\n}\n\n// Run executes the extraction.\nfunc (e *extractor) Run() error {\n\te.logger.Info(\"Parsing captured file for trace\", zap.String(\"trace_id\", e.traceID))\n\n\tvar (\n\t\tspans []uimodel.Span\n\t\tspan  *uimodel.Span\n\t\terr   error\n\t)\n\tfor span, err = e.reader.NextSpan(); err == nil; span, err = e.reader.NextSpan() {\n\t\tif string(span.TraceID) == e.traceID {\n\t\t\tspans = append(spans, *span)\n\t\t}\n\t}\n\tif !errors.Is(err, errNoMoreSpans) {\n\t\treturn fmt.Errorf(\"failed when scanning the file: %w\", err)\n\t}\n\ttrace := uimodel.Trace{\n\t\tTraceID:   uimodel.TraceID(e.traceID),\n\t\tSpans:     spans,\n\t\tProcesses: make(map[uimodel.ProcessID]uimodel.Process),\n\t}\n\t// (ys) The following is not exactly correct because it does not dedupe the processes,\n\t// but I don't think it affects the UI.\n\tfor i := range spans {\n\t\tspan := &spans[i]\n\t\tpid := uimodel.ProcessID(fmt.Sprintf(\"p%d\", i))\n\t\ttrace.Processes[pid] = *span.Process\n\t\tspan.Process = nil\n\t\tspan.ProcessID = pid\n\t}\n\tjsonBytes, err := json.Marshal(trace)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal UI trace: %w\", err)\n\t}\n\te.uiFile.WriteString(`{\"data\": [`)\n\te.uiFile.Write(jsonBytes)\n\te.uiFile.WriteString(`]}`)\n\te.uiFile.Sync()\n\te.uiFile.Close()\n\te.logger.Sugar().Infof(\"Wrote spans to UI file %s\", e.uiFile.Name())\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/extractor_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\ntype UITrace struct {\n\tData []model.Trace\n}\n\nfunc TestExtractorTraceSuccess(t *testing.T) {\n\tinputFile := \"fixtures/trace_success.json\"\n\toutputFile := \"fixtures/trace_success_ui_anonymized.json\"\n\tdefer os.Remove(outputFile)\n\n\treader, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\textractor, err := newExtractor(\n\t\toutputFile,\n\t\t\"2be38093ead7a083\",\n\t\treader,\n\t\tzap.NewNop(),\n\t)\n\trequire.NoError(t, err)\n\n\terr = extractor.Run()\n\trequire.NoError(t, err)\n\n\tvar trace UITrace\n\tloadJSON(t, outputFile, &trace)\n\n\tfor i := range trace.Data {\n\t\tfor j := range trace.Data[i].Spans {\n\t\t\tassert.Equal(t, model.SpanKindKey, trace.Data[i].Spans[j].Tags[0].Key)\n\t\t}\n\t}\n}\n\nfunc TestExtractorTraceOutputFileError(t *testing.T) {\n\tinputFile := \"fixtures/trace_success.json\"\n\toutputFile := \"fixtures/trace_success_ui_anonymized.json\"\n\tdefer os.Remove(outputFile)\n\n\treader, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\terr = os.Chmod(\"fixtures\", 0o000)\n\trequire.NoError(t, err)\n\tdefer os.Chmod(\"fixtures\", 0o755)\n\n\t_, err = newExtractor(\n\t\toutputFile,\n\t\t\"2be38093ead7a083\",\n\t\treader,\n\t\tzap.NewNop(),\n\t)\n\trequire.ErrorContains(t, err, \"cannot create output file\")\n}\n\nfunc TestExtractorTraceScanError(t *testing.T) {\n\tinputFile := \"fixtures/trace_scan_error.json\"\n\toutputFile := \"fixtures/trace_scan_error_ui_anonymized.json\"\n\tdefer os.Remove(outputFile)\n\n\treader, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\textractor, err := newExtractor(\n\t\toutputFile,\n\t\t\"2be38093ead7a083\",\n\t\treader,\n\t\tzap.NewNop(),\n\t)\n\trequire.NoError(t, err)\n\n\terr = extractor.Run()\n\trequire.ErrorContains(t, err, \"failed when scanning the file\")\n}\n\nfunc loadJSON(t *testing.T, fileName string, i any) {\n\tb, err := os.ReadFile(fileName)\n\trequire.NoError(t, err)\n\terr = json.Unmarshal(b, i)\n\trequire.NoError(t, err, \"Failed to parse json fixture file %s\", fileName)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/fixtures/trace_empty.json",
    "content": ""
  },
  {
    "path": "cmd/anonymizer/app/uiconv/fixtures/trace_invalid_json.json",
    "content": "[{\"traceID\":\"2be38093ead7a083\",\"spanID\":\"7bd66f09ba90ea3d\",\"duration\": \"invalid\"}\n]"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/fixtures/trace_scan_error.json",
    "content": "[{\"traceID\":\"2be38093ead7a083\",\"spanID\":\"7606ddfe69932d34\",\"duration\":267037},\n]"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/fixtures/trace_success.json",
    "content": "[{\"traceID\":\"2be38093ead7a083\",\"spanID\":\"7606ddfe69932d34\",\"flags\":1,\"operationName\":\"a071653098f9250d\",\"references\":[{\"refType\":\"CHILD_OF\",\"traceID\":\"2be38093ead7a083\",\"spanID\":\"492770a15935810f\"}],\"startTime\":1605223981761425,\"duration\":267037,\"tags\":[{\"key\":\"span.kind\",\"type\":\"string\",\"value\":\"server\"}],\"logs\":[],\"process\":{\"serviceName\":\"16af988c443cff37\",\"tags\":[]},\"warnings\":null},\n  {\"traceID\":\"2be38093ead7a083\",\"spanID\":\"7bd66f09ba90ea3d\",\"flags\":1,\"operationName\":\"471418097747d04a\",\"references\":[{\"refType\":\"CHILD_OF\",\"traceID\":\"2be38093ead7a083\",\"spanID\":\"7606ddfe69932d34\"}],\"startTime\":1605223981965074,\"duration\":32782,\"tags\":[{\"key\":\"span.kind\",\"type\":\"string\",\"value\":\"client\"},{\"key\":\"error\",\"type\":\"bool\",\"value\":\"true\"}],\"logs\":[],\"process\":{\"serviceName\":\"3c220036602f839e\",\"tags\":[]},\"warnings\":null}\n]"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/fixtures/trace_wrong_format.json",
    "content": "{\"traceID\":\"2be38093ead7a083\",\"spanID\":\"7606ddfe69932d34\",\"duration\":267037}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/module.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"go.uber.org/zap\"\n)\n\n// Config for the extractor.\ntype Config struct {\n\tCapturedFile string `yaml:\"captured_file\"`\n\tUIFile       string `yaml:\"ui_file\"`\n\tTraceID      string `yaml:\"trace_id\"`\n}\n\n// Extract reads anonymized file, finds spans for a given trace,\n// and writes out that trace in the UI format.\nfunc Extract(config Config, logger *zap.Logger) error {\n\treader, err := newSpanReader(config.CapturedFile, logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\text, err := newExtractor(config.UIFile, config.TraceID, reader, logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn ext.Run()\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/module_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nfunc TestModule_TraceSuccess(t *testing.T) {\n\tinputFile := \"fixtures/trace_success.json\"\n\toutputFile := \"fixtures/trace_success_ui_anonymized.json\"\n\tdefer os.Remove(outputFile)\n\n\tconfig := Config{\n\t\tCapturedFile: inputFile,\n\t\tUIFile:       outputFile,\n\t\tTraceID:      \"2be38093ead7a083\",\n\t}\n\terr := Extract(config, zap.NewNop())\n\trequire.NoError(t, err)\n\n\tvar trace UITrace\n\tloadJSON(t, outputFile, &trace)\n\n\tfor i := range trace.Data {\n\t\tfor j := range trace.Data[i].Spans {\n\t\t\tassert.Equal(t, model.SpanKindKey, trace.Data[i].Spans[j].Tags[0].Key)\n\t\t}\n\t}\n}\n\nfunc TestModule_TraceNonExistent(t *testing.T) {\n\tinputFile := \"fixtures/trace_non_existent.json\"\n\toutputFile := \"fixtures/trace_non_existent_ui_anonymized.json\"\n\tdefer os.Remove(outputFile)\n\n\tconfig := Config{\n\t\tCapturedFile: inputFile,\n\t\tUIFile:       outputFile,\n\t\tTraceID:      \"2be38093ead7a083\",\n\t}\n\terr := Extract(config, zap.NewNop())\n\trequire.ErrorContains(t, err, \"cannot open captured file\")\n}\n\nfunc TestModule_TraceOutputFileError(t *testing.T) {\n\tinputFile := \"fixtures/trace_success.json\"\n\toutputFile := \"fixtures/trace_success_ui_anonymized.json\"\n\tdefer os.Remove(outputFile)\n\n\tconfig := Config{\n\t\tCapturedFile: inputFile,\n\t\tUIFile:       outputFile,\n\t\tTraceID:      \"2be38093ead7a083\",\n\t}\n\n\terr := os.Chmod(\"fixtures\", 0o550)\n\trequire.NoError(t, err)\n\tdefer os.Chmod(\"fixtures\", 0o755)\n\n\terr = Extract(config, zap.NewNop())\n\trequire.ErrorContains(t, err, \"cannot create output file\")\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/reader.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/uimodel\"\n)\n\nvar errNoMoreSpans = errors.New(\"no more spans\")\n\n// spanReader loads previously captured spans from a file.\ntype spanReader struct {\n\tlogger       *zap.Logger\n\tcapturedFile *os.File\n\treader       *bufio.Reader\n\tspansRead    int\n\teofReached   bool\n}\n\n// newSpanReader creates a spanReader.\nfunc newSpanReader(capturedFile string, logger *zap.Logger) (*spanReader, error) {\n\tcf, err := os.OpenFile(capturedFile, os.O_RDONLY, os.ModePerm)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot open captured file: %w\", err)\n\t}\n\tlogger.Sugar().Infof(\"Reading captured spans from file %s\", capturedFile)\n\n\treturn &spanReader{\n\t\tlogger:       logger,\n\t\tcapturedFile: cf,\n\t\treader:       bufio.NewReader(cf),\n\t}, nil\n}\n\n// NextSpan reads the next span from the capture file, or returns errNoMoreSpans.\nfunc (r *spanReader) NextSpan() (*uimodel.Span, error) {\n\tif r.eofReached {\n\t\treturn nil, errNoMoreSpans\n\t}\n\tif r.spansRead == 0 {\n\t\tb, err := r.reader.ReadByte()\n\t\tif err != nil {\n\t\t\tr.eofReached = true\n\t\t\treturn nil, fmt.Errorf(\"cannot read file: %w\", err)\n\t\t}\n\t\tif b != '[' {\n\t\t\tr.eofReached = true\n\t\t\treturn nil, errors.New(\"file must begin with '['\")\n\t\t}\n\t}\n\ts, err := r.reader.ReadString('\\n')\n\tif err != nil {\n\t\tr.eofReached = true\n\t\treturn nil, fmt.Errorf(\"cannot read file: %w\", err)\n\t}\n\tif s[len(s)-2] == ',' { // all but last span lines end with ,\\n\n\t\ts = s[0 : len(s)-2]\n\t} else {\n\t\tr.eofReached = true\n\t}\n\tvar span uimodel.Span\n\terr = json.Unmarshal([]byte(s), &span)\n\tif err != nil {\n\t\tr.eofReached = true\n\t\treturn nil, fmt.Errorf(\"cannot unmarshal span: %w; %s\", err, s)\n\t}\n\tr.spansRead++\n\tif r.spansRead%1000 == 0 {\n\t\tr.logger.Info(\"Scan progress\", zap.Int(\"span_count\", r.spansRead))\n\t}\n\treturn &span, nil\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/uiconv/reader_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uiconv\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestReaderTraceSuccess(t *testing.T) {\n\tinputFile := \"fixtures/trace_success.json\"\n\tr, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\ts1, err := r.NextSpan()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"a071653098f9250d\", s1.OperationName)\n\tassert.Equal(t, 1, r.spansRead)\n\tassert.False(t, r.eofReached)\n\n\tr.spansRead = 999\n\n\ts2, err := r.NextSpan()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"471418097747d04a\", s2.OperationName)\n\tassert.Equal(t, 1000, r.spansRead)\n\tassert.True(t, r.eofReached)\n\n\t_, err = r.NextSpan()\n\trequire.Equal(t, errNoMoreSpans, err)\n\tassert.Equal(t, 1000, r.spansRead)\n\tassert.True(t, r.eofReached)\n}\n\nfunc TestReaderTraceNonExistent(t *testing.T) {\n\tinputFile := \"fixtures/trace_non_existent.json\"\n\t_, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.ErrorContains(t, err, \"cannot open captured file\")\n}\n\nfunc TestReaderTraceEmpty(t *testing.T) {\n\tinputFile := \"fixtures/trace_empty.json\"\n\tr, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\t_, err = r.NextSpan()\n\trequire.ErrorContains(t, err, \"cannot read file\")\n\tassert.Equal(t, 0, r.spansRead)\n\tassert.True(t, r.eofReached)\n}\n\nfunc TestReaderTraceWrongFormat(t *testing.T) {\n\tinputFile := \"fixtures/trace_wrong_format.json\"\n\tr, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\t_, err = r.NextSpan()\n\trequire.Equal(t, \"file must begin with '['\", err.Error())\n\tassert.Equal(t, 0, r.spansRead)\n\tassert.True(t, r.eofReached)\n}\n\nfunc TestReaderTraceInvalidJson(t *testing.T) {\n\tinputFile := \"fixtures/trace_invalid_json.json\"\n\tr, err := newSpanReader(inputFile, zap.NewNop())\n\trequire.NoError(t, err)\n\n\t_, err = r.NextSpan()\n\trequire.ErrorContains(t, err, \"cannot unmarshal span\")\n\tassert.Equal(t, 0, r.spansRead)\n\tassert.True(t, r.eofReached)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/writer/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage writer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/writer/writer.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage writer\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/cmd/anonymizer/app/anonymizer\"\n)\n\nvar ErrMaxSpansCountReached = errors.New(\"max spans count reached\")\n\n// Config contains parameters to NewWriter.\ntype Config struct {\n\tMaxSpansCount  int                `yaml:\"max_spans_count\" name:\"max_spans_count\"`\n\tCapturedFile   string             `yaml:\"captured_file\" name:\"captured_file\"`\n\tAnonymizedFile string             `yaml:\"anonymized_file\" name:\"anonymized_file\"`\n\tMappingFile    string             `yaml:\"mapping_file\" name:\"mapping_file\"`\n\tAnonymizerOpts anonymizer.Options `yaml:\"anonymizer\" name:\"anonymizer\"`\n}\n\n// Writer is a span Writer that obfuscates the span and writes it to a JSON file.\ntype Writer struct {\n\tconfig         Config\n\tlock           sync.Mutex\n\tlogger         *zap.Logger\n\tcapturedFile   *os.File\n\tanonymizedFile *os.File\n\tanonymizer     *anonymizer.Anonymizer\n\tspanCount      int\n}\n\n// New creates an Writer\nfunc New(config Config, logger *zap.Logger) (*Writer, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogger.Sugar().Infof(\"Current working dir is %s\", wd)\n\n\tcf, err := os.OpenFile(config.CapturedFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot create output file: %w\", err)\n\t}\n\tlogger.Sugar().Infof(\"Writing captured spans to file %s\", config.CapturedFile)\n\n\taf, err := os.OpenFile(config.AnonymizedFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot create output file: %w\", err)\n\t}\n\tlogger.Sugar().Infof(\"Writing anonymized spans to file %s\", config.AnonymizedFile)\n\n\t_, err = cf.WriteString(\"[\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot write tp output file: %w\", err)\n\t}\n\t_, err = af.WriteString(\"[\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot write tp output file: %w\", err)\n\t}\n\n\toptions := anonymizer.Options{\n\t\tHashStandardTags: config.AnonymizerOpts.HashStandardTags,\n\t\tHashCustomTags:   config.AnonymizerOpts.HashCustomTags,\n\t\tHashLogs:         config.AnonymizerOpts.HashLogs,\n\t\tHashProcess:      config.AnonymizerOpts.HashProcess,\n\t}\n\n\treturn &Writer{\n\t\tconfig:         config,\n\t\tlogger:         logger,\n\t\tcapturedFile:   cf,\n\t\tanonymizedFile: af,\n\t\tanonymizer:     anonymizer.New(config.MappingFile, options, logger),\n\t}, nil\n}\n\n// WriteSpan anonymized the span and appends it as JSON to w.file.\nfunc (w *Writer) WriteSpan(msg *model.Span) error {\n\tw.lock.Lock()\n\tdefer w.lock.Unlock()\n\n\tout := new(bytes.Buffer)\n\tif err := new(jsonpb.Marshaler).Marshal(out, msg); err != nil {\n\t\treturn err\n\t}\n\tif w.spanCount > 0 {\n\t\tw.capturedFile.WriteString(\",\\n\")\n\t}\n\tw.capturedFile.Write(out.Bytes())\n\tw.capturedFile.Sync()\n\n\tspan := w.anonymizer.AnonymizeSpan(msg)\n\n\tdat, err := json.Marshal(span)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif w.spanCount > 0 {\n\t\tw.anonymizedFile.WriteString(\",\\n\")\n\t}\n\tif _, err := w.anonymizedFile.Write(dat); err != nil {\n\t\treturn err\n\t}\n\tw.anonymizedFile.Sync()\n\n\tw.spanCount++\n\tif w.spanCount%100 == 0 {\n\t\tw.logger.Info(\"progress\", zap.Int(\"numSpans\", w.spanCount))\n\t}\n\n\tif w.config.MaxSpansCount > 0 && w.spanCount >= w.config.MaxSpansCount {\n\t\tw.logger.Info(\"Saved enough spans, exiting...\")\n\t\tw.Close()\n\t\treturn ErrMaxSpansCountReached\n\t}\n\n\treturn nil\n}\n\n// Close closes the captured and anonymized files.\nfunc (w *Writer) Close() {\n\tw.capturedFile.WriteString(\"\\n]\\n\")\n\tw.capturedFile.Close()\n\tw.anonymizedFile.WriteString(\"\\n]\\n\")\n\tw.anonymizedFile.Close()\n\tw.anonymizer.Stop()\n\tw.anonymizer.SaveMapping()\n}\n"
  },
  {
    "path": "cmd/anonymizer/app/writer/writer_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage writer\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nvar tags = []model.KeyValue{\n\tmodel.Bool(\"error\", true),\n\tmodel.String(\"http.method\", http.MethodPost),\n\tmodel.Bool(\"foobar\", true),\n}\n\nvar traceID = model.NewTraceID(1, 2)\n\nvar span = &model.Span{\n\tTraceID: traceID,\n\tSpanID:  model.NewSpanID(1),\n\tProcess: &model.Process{\n\t\tServiceName: \"serviceName\",\n\t\tTags:        tags,\n\t},\n\tOperationName: \"operationName\",\n\tTags:          tags,\n\tLogs: []model.Log{\n\t\t{\n\t\t\tTimestamp: time.Now(),\n\t\t\tFields: []model.KeyValue{\n\t\t\t\tmodel.String(\"logKey\", \"logValue\"),\n\t\t\t},\n\t\t},\n\t},\n\tDuration:  time.Second * 5,\n\tStartTime: time.Unix(300, 0),\n}\n\nfunc TestNew(t *testing.T) {\n\tnopLogger := zap.NewNop()\n\ttempDir := t.TempDir()\n\n\tt.Run(\"no error\", func(t *testing.T) {\n\t\tconfig := Config{\n\t\t\tMaxSpansCount:  10,\n\t\t\tCapturedFile:   tempDir + \"/captured.json\",\n\t\t\tAnonymizedFile: tempDir + \"/anonymized.json\",\n\t\t\tMappingFile:    tempDir + \"/mapping.json\",\n\t\t}\n\t\twriter, err := New(config, nopLogger)\n\t\trequire.NoError(t, err)\n\t\tdefer writer.Close()\n\t})\n\n\tt.Run(\"CapturedFile does not exist\", func(t *testing.T) {\n\t\tconfig := Config{\n\t\t\tCapturedFile:   tempDir + \"/nonexistent_directory/captured.json\",\n\t\t\tAnonymizedFile: tempDir + \"/anonymized.json\",\n\t\t\tMappingFile:    tempDir + \"/mapping.json\",\n\t\t}\n\t\t_, err := New(config, nopLogger)\n\t\trequire.ErrorContains(t, err, \"cannot create output file\")\n\t})\n\n\tt.Run(\"AnonymizedFile does not exist\", func(t *testing.T) {\n\t\tconfig := Config{\n\t\t\tCapturedFile:   tempDir + \"/captured.json\",\n\t\t\tAnonymizedFile: tempDir + \"/nonexistent_directory/anonymized.json\",\n\t\t\tMappingFile:    tempDir + \"/mapping.json\",\n\t\t}\n\t\t_, err := New(config, nopLogger)\n\t\trequire.ErrorContains(t, err, \"cannot create output file\")\n\t})\n}\n\nfunc TestWriter_WriteSpan(t *testing.T) {\n\tnopLogger := zap.NewNop()\n\tt.Run(\"write span\", func(t *testing.T) {\n\t\ttempDir := t.TempDir()\n\t\tconfig := Config{\n\t\t\tMaxSpansCount:  10,\n\t\t\tCapturedFile:   tempDir + \"/captured.json\",\n\t\t\tAnonymizedFile: tempDir + \"/anonymized.json\",\n\t\t\tMappingFile:    tempDir + \"/mapping.json\",\n\t\t}\n\n\t\twriter, err := New(config, nopLogger)\n\t\trequire.NoError(t, err)\n\t\tdefer writer.Close()\n\n\t\tfor range 9 {\n\t\t\terr = writer.WriteSpan(span)\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t})\n\tt.Run(\"write span with MaxSpansCount\", func(t *testing.T) {\n\t\ttempDir := t.TempDir()\n\t\tconfig := Config{\n\t\t\tMaxSpansCount:  1,\n\t\t\tCapturedFile:   tempDir + \"/captured.json\",\n\t\t\tAnonymizedFile: tempDir + \"/anonymized.json\",\n\t\t\tMappingFile:    tempDir + \"/mapping.json\",\n\t\t}\n\n\t\twriter, err := New(config, zap.NewNop())\n\t\trequire.NoError(t, err)\n\t\tdefer writer.Close()\n\n\t\terr = writer.WriteSpan(span)\n\t\trequire.ErrorIs(t, err, ErrMaxSpansCountReached)\n\t})\n}\n"
  },
  {
    "path": "cmd/anonymizer/main.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/anonymizer/app\"\n\t\"github.com/jaegertracing/jaeger/cmd/anonymizer/app/anonymizer\"\n\t\"github.com/jaegertracing/jaeger/cmd/anonymizer/app/query\"\n\t\"github.com/jaegertracing/jaeger/cmd/anonymizer/app/uiconv\"\n\t\"github.com/jaegertracing/jaeger/cmd/anonymizer/app/writer\"\n\t\"github.com/jaegertracing/jaeger/internal/version\"\n)\n\nvar logger, _ = zap.NewDevelopment()\n\nfunc main() {\n\toptions := app.Options{}\n\n\tcommand := &cobra.Command{\n\t\tUse:   \"jaeger-anonymizer\",\n\t\tShort: \"Jaeger anonymizer hashes fields of a trace for easy sharing\",\n\t\tLong:  `Jaeger anonymizer queries Jaeger query for a trace, anonymizes fields, and store in file`,\n\t\tRun: func(_ *cobra.Command, _ /* args */ []string) {\n\t\t\tprefix := options.OutputDir + \"/\" + options.TraceID\n\t\t\tconf := writer.Config{\n\t\t\t\tMaxSpansCount:  options.MaxSpansCount,\n\t\t\t\tCapturedFile:   prefix + \".original.json\",\n\t\t\t\tAnonymizedFile: prefix + \".anonymized.json\",\n\t\t\t\tMappingFile:    prefix + \".mapping.json\",\n\t\t\t\tAnonymizerOpts: anonymizer.Options{\n\t\t\t\t\tHashStandardTags: options.HashStandardTags,\n\t\t\t\t\tHashCustomTags:   options.HashCustomTags,\n\t\t\t\t\tHashLogs:         options.HashLogs,\n\t\t\t\t\tHashProcess:      options.HashProcess,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tw, err := writer.New(conf, logger)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Fatal(\"error while creating writer object\", zap.Error(err))\n\t\t\t}\n\n\t\t\tquery, err := query.New(options.QueryGRPCHostPort)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Fatal(\"error while creating query object\", zap.Error(err))\n\t\t\t}\n\n\t\t\tspans, err := query.QueryTrace(\n\t\t\t\toptions.TraceID,\n\t\t\t\tinitTime(options.StartTime),\n\t\t\t\tinitTime(options.EndTime),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Fatal(\"error while querying for trace\", zap.Error(err))\n\t\t\t}\n\t\t\tif err := query.Close(); err != nil {\n\t\t\t\tlogger.Error(\"Failed to close grpc client connection\", zap.Error(err))\n\t\t\t}\n\n\t\t\tfor i := range spans {\n\t\t\t\tspan := &spans[i]\n\t\t\t\tif err := w.WriteSpan(span); err != nil {\n\t\t\t\t\tif errors.Is(err, writer.ErrMaxSpansCountReached) {\n\t\t\t\t\t\tlogger.Info(\"max spans count reached\")\n\t\t\t\t\t\tos.Exit(0)\n\t\t\t\t\t}\n\t\t\t\t\tlogger.Error(\"error while writing span\", zap.Error(err))\n\t\t\t\t}\n\t\t\t}\n\t\t\tw.Close()\n\n\t\t\tuiCfg := uiconv.Config{\n\t\t\t\tCapturedFile: conf.AnonymizedFile,\n\t\t\t\tUIFile:       prefix + \".anonymized-ui-trace.json\",\n\t\t\t\tTraceID:      options.TraceID,\n\t\t\t}\n\t\t\tif err := uiconv.Extract(uiCfg, logger); err != nil {\n\t\t\t\tlogger.Fatal(\"error while extracing UI trace\", zap.Error(err))\n\t\t\t}\n\t\t\tlogger.Sugar().Infof(\"Wrote UI-compatible anonymized file to %s\", uiCfg.UIFile)\n\t\t},\n\t}\n\n\toptions.AddFlags(command)\n\n\tcommand.AddCommand(version.Command())\n\n\tif err := command.Execute(); err != nil {\n\t\tfmt.Println(err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc initTime(ts int64) time.Time {\n\tvar t time.Time\n\tif ts != 0 {\n\t\tt = time.Unix(0, ts)\n\t}\n\treturn t\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/.gitignore",
    "content": "es-index-cleaner-*-*\n"
  },
  {
    "path": "cmd/es-index-cleaner/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nARG base_image\n\nFROM $base_image AS release\nARG TARGETARCH\nARG USER_UID=10001\nCOPY es-index-cleaner-linux-$TARGETARCH /go/bin/es-index-cleaner-linux\nENTRYPOINT [\"/go/bin/es-index-cleaner-linux\"]\nUSER ${USER_UID}\n"
  },
  {
    "path": "cmd/es-index-cleaner/README.md",
    "content": "# jaeger-es-index-cleaner\n\nIt is common to only keep observability data for a limited time.\nHowever, Elasticsearch does not support expiring of old data via TTL.\nTo help with this task, `jaeger-es-index-cleaner` can be used to purge\nold Jaeger indices. For example, to delete indices older than 14 days:\n\n```\ndocker run -it --rm --net=host -e ROLLOVER=true \\\n  jaegertracing/jaeger-es-index-cleaner:latest \\\n  14 \\\n  http://localhost:9200\n```\n\nAnother alternative is to use [Elasticsearch Curator][curator].\n\n[curator]: https://www.elastic.co/guide/en/elasticsearch/client/curator/current/about.html\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/cutoff_time.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"time\"\n)\n\nfunc CalculateDeletionCutoff(currTime time.Time, numOfDays int, relativeIndexEnabled bool) time.Time {\n\tyear, month, day := currTime.Date()\n\t// tomorrow midnight\n\tcutoffTime := time.Date(year, month, day, 0, 0, 0, 0, time.UTC).AddDate(0, 0, 1)\n\tif relativeIndexEnabled {\n\t\tcutoffTime = currTime\n\t}\n\treturn cutoffTime.Add(-time.Hour * 24 * time.Duration(numOfDays))\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/cutoff_time_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCalculateDeletionCutoff(t *testing.T) {\n\ttime20250309163053 := time.Date(2025, time.March, 9, 16, 30, 53, 0, time.UTC)\n\ttests := []struct {\n\t\tname                 string\n\t\tcurrTime             time.Time\n\t\tnumOfDays            int\n\t\trelativeIndexEnabled bool\n\t\texpectedCutoff       time.Time\n\t}{\n\t\t{\n\t\t\tname:                 \"get today's midnight\",\n\t\t\tcurrTime:             time20250309163053,\n\t\t\tnumOfDays:            1,\n\t\t\trelativeIndexEnabled: false,\n\t\t\texpectedCutoff:       time.Date(2025, time.March, 9, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:                 \"get exactly 24 hours before execution time\",\n\t\t\tcurrTime:             time20250309163053,\n\t\t\tnumOfDays:            1,\n\t\t\trelativeIndexEnabled: true,\n\t\t\texpectedCutoff:       time.Date(2025, time.March, 8, 16, 30, 53, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:                 \"get the current time if numOfDays is 0 and relativeIndexEnabled is true\",\n\t\t\tcurrTime:             time20250309163053,\n\t\t\tnumOfDays:            0,\n\t\t\trelativeIndexEnabled: true,\n\t\t\texpectedCutoff:       time20250309163053,\n\t\t},\n\t\t{\n\t\t\tname:                 \"get tomorrow's midnight if numOfDays is 0  relativeIndexEnabled is False\",\n\t\t\tcurrTime:             time20250309163053,\n\t\t\tnumOfDays:            0,\n\t\t\trelativeIndexEnabled: false,\n\t\t\texpectedCutoff:       time.Date(2025, time.March, 10, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:                 \"get (numOfDays-1)'s midnight\",\n\t\t\tcurrTime:             time20250309163053,\n\t\t\tnumOfDays:            3,\n\t\t\trelativeIndexEnabled: false,\n\t\t\texpectedCutoff:       time.Date(2025, time.March, 7, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:                 \"get exactly (24*numOfDays) hours before execution time\",\n\t\t\tcurrTime:             time20250309163053,\n\t\t\tnumOfDays:            3,\n\t\t\trelativeIndexEnabled: true,\n\t\t\texpectedCutoff:       time.Date(2025, time.March, 6, 16, 30, 53, 0, time.UTC),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcutoffTime := CalculateDeletionCutoff(time20250309163053, test.numOfDays, test.relativeIndexEnabled)\n\t\t\tassert.Equal(t, test.expectedCutoff, cutoffTime)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/flags.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\t\"go.opentelemetry.io/collector/featuregate\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config/tlscfg\"\n)\n\nconst (\n\tindexPrefix        = \"index-prefix\"\n\tarchive            = \"archive\"\n\trollover           = \"rollover\"\n\ttimeout            = \"timeout\"\n\tindexDateSeparator = \"index-date-separator\"\n\tusername           = \"es.username\"\n\tpassword           = \"es.password\"\n)\n\nvar tlsFlagsCfg = tlscfg.ClientFlagsConfig{Prefix: \"es\"}\n\n// Config holds configuration for index cleaner binary.\ntype Config struct {\n\tIndexPrefix              string\n\tArchive                  bool\n\tRollover                 bool\n\tMasterNodeTimeoutSeconds int\n\tIndexDateSeparator       string\n\tUsername                 string\n\tPassword                 string //nolint:gosec // G117\n\tTLSEnabled               bool\n\tTLSConfig                configtls.ClientConfig\n}\n\n// AddFlags adds flags for TLS to the FlagSet.\nfunc (*Config) AddFlags(flags *flag.FlagSet) {\n\tflags.String(indexPrefix, \"\", \"Index prefix\")\n\tflags.Bool(archive, false, \"Whether to remove archive indices. It works only for rollover\")\n\tflags.Bool(rollover, false, \"Whether to remove indices created by rollover\")\n\tflags.Int(timeout, 120, \"Number of seconds to wait for master node response\")\n\tflags.String(indexDateSeparator, \"-\", \"Index date separator\")\n\tflags.String(username, \"\", \"The username required by storage\")\n\tflags.String(password, \"\", \"The password required by storage\")\n\ttlsFlagsCfg.AddFlags(flags)\n\tfeaturegate.GlobalRegistry().RegisterFlags(flags)\n}\n\n// InitFromViper initializes config from viper.Viper.\nfunc (c *Config) InitFromViper(v *viper.Viper) error {\n\tc.IndexPrefix = v.GetString(indexPrefix)\n\tif c.IndexPrefix != \"\" {\n\t\tc.IndexPrefix += \"-\"\n\t}\n\n\tc.Archive = v.GetBool(archive)\n\tc.Rollover = v.GetBool(rollover)\n\tc.MasterNodeTimeoutSeconds = v.GetInt(timeout)\n\tc.IndexDateSeparator = v.GetString(indexDateSeparator)\n\tc.Username = v.GetString(username)\n\tc.Password = v.GetString(password)\n\ttlsCfg, err := tlsFlagsCfg.InitFromViper(v)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.TLSConfig = tlsCfg\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/flags_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBindFlags(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tc.AddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--index-prefix=tenant1\",\n\t\t\"--rollover=true\",\n\t\t\"--archive=true\",\n\t\t\"--timeout=150\",\n\t\t\"--index-date-separator=@\",\n\t\t\"--es.username=admin\",\n\t\t\"--es.password=admin\",\n\t})\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, c.InitFromViper(v))\n\tassert.Equal(t, \"tenant1-\", c.IndexPrefix)\n\tassert.True(t, c.Rollover)\n\tassert.True(t, c.Archive)\n\tassert.Equal(t, 150, c.MasterNodeTimeoutSeconds)\n\tassert.Equal(t, \"@\", c.IndexDateSeparator)\n\tassert.Equal(t, \"admin\", c.Username)\n\tassert.Equal(t, \"admin\", c.Password)\n}\n\nfunc TestInitFromViper_TLSError(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tc.AddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--es.tls.ca=/nonexistent/ca.crt\",\n\t})\n\trequire.NoError(t, err)\n\n\terr = c.InitFromViper(v)\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/index_filter.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/filter\"\n)\n\n// IndexFilter holds configuration for index filtering.\ntype IndexFilter struct {\n\t// Index prefix.\n\tIndexPrefix string\n\t// Separator between date fragments.\n\tIndexDateSeparator string\n\t// Whether to filter archive indices.\n\tArchive bool\n\t// Whether to filter rollover indices.\n\tRollover bool\n\t// Indices created before this date will be deleted.\n\tDeleteBeforeThisDate time.Time\n}\n\n// Filter filters indices.\nfunc (i *IndexFilter) Filter(indices []client.Index) []client.Index {\n\tindices = i.filterByPattern(indices)\n\treturn filter.ByDate(indices, i.DeleteBeforeThisDate)\n}\n\nfunc (i *IndexFilter) filterByPattern(indices []client.Index) []client.Index {\n\tvar reg *regexp.Regexp\n\tswitch {\n\tcase i.Archive:\n\t\t// archive works only for rollover\n\t\treg, _ = regexp.Compile(fmt.Sprintf(\"^%sjaeger-span-archive-\\\\d{6}\", i.IndexPrefix))\n\tcase i.Rollover:\n\t\treg, _ = regexp.Compile(fmt.Sprintf(\"^%sjaeger-(span|service|dependencies|sampling)-\\\\d{6}\", i.IndexPrefix))\n\tdefault:\n\t\treg, _ = regexp.Compile(fmt.Sprintf(\"^%sjaeger-(span|service|dependencies|sampling)-\\\\d{4}%s\\\\d{2}%s\\\\d{2}\", i.IndexPrefix, i.IndexDateSeparator, i.IndexDateSeparator))\n\t}\n\n\tvar filtered []client.Index\n\tfor _, in := range indices {\n\t\tif reg.MatchString(in.Index) {\n\t\t\t// index in write alias cannot be removed\n\t\t\tif in.Aliases[i.IndexPrefix+\"jaeger-span-write\"] ||\n\t\t\t\tin.Aliases[i.IndexPrefix+\"jaeger-service-write\"] ||\n\t\t\t\tin.Aliases[i.IndexPrefix+\"jaeger-span-archive-write\"] ||\n\t\t\t\tin.Aliases[i.IndexPrefix+\"jaeger-dependencies-write\"] ||\n\t\t\t\tin.Aliases[i.IndexPrefix+\"jaeger-sampling-write\"] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfiltered = append(filtered, in)\n\t\t}\n\t}\n\treturn filtered\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/index_filter_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nfunc TestIndexFilter(t *testing.T) {\n\trunIndexFilterTest(t, \"\")\n}\n\nfunc TestIndexFilterWithPrefix(t *testing.T) {\n\trunIndexFilterTest(t, \"tenant1-\")\n}\n\nfunc runIndexFilterTest(t *testing.T, prefix string) {\n\ttime20200807 := time.Date(2020, time.August, 6, 0, 0, 0, 0, time.UTC).AddDate(0, 0, 1)\n\tindices := []client.Index{\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-2020-08-06\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-2020-08-05\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-service-2020-08-06\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-service-2020-08-05\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-dependencies-2020-08-06\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-dependencies-2020-08-05\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-sampling-2020-08-06\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-sampling-2020-08-05\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-archive\",\n\t\t\tCreationTime: time.Date(2020, time.August, 1, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-000001\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\tprefix + \"jaeger-span-read\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-000002\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\tprefix + \"jaeger-span-read\":  true,\n\t\t\t\tprefix + \"jaeger-span-write\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-service-000001\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\tprefix + \"jaeger-service-read\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-service-000002\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\tprefix + \"jaeger-service-read\":  true,\n\t\t\t\tprefix + \"jaeger-service-write\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-archive-000001\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\tprefix + \"jaeger-span-archive-read\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        prefix + \"jaeger-span-archive-000002\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\tprefix + \"jaeger-span-archive-read\":  true,\n\t\t\t\tprefix + \"jaeger-span-archive-write\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"other-jaeger-span-2020-08-05\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"other-jaeger-service-2020-08-06\",\n\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"other-bar-jaeger-span-000002\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"other-jaeger-span-read\":  true,\n\t\t\t\t\"other-jaeger-span-write\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"otherfoo-jaeger-span-archive\",\n\t\t\tCreationTime: time.Date(2020, time.August, 1, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases:      map[string]bool{},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"foo-jaeger-span-archive-000001\",\n\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"foo-jaeger-span-archive-read\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\tfilter   *IndexFilter\n\t\texpected []client.Index\n\t}{\n\t\t{\n\t\t\tname: \"normal indices, remove older than 2 days\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              false,\n\t\t\t\tRollover:             false,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(2)),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"normal indices, remove older 1 days\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              false,\n\t\t\t\tRollover:             false,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(1)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-service-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-dependencies-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-sampling-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"normal indices, remove older 0 days - it should remove all indices\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              false,\n\t\t\t\tRollover:             false,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(0)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-2020-08-06\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-service-2020-08-06\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-service-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-dependencies-2020-08-06\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-dependencies-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-sampling-2020-08-06\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 6, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-sampling-2020-08-05\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"archive indices, remove older 1 days - archive works only for rollover\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              true,\n\t\t\t\tRollover:             false,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(1)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-archive-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-span-archive-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollover indices, remove older 1 days\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              false,\n\t\t\t\tRollover:             true,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(1)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-span-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-service-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-service-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollover indices, remove older 0 days, index in write alias cannot be removed\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              false,\n\t\t\t\tRollover:             true,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(0)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-span-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-service-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-service-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollover archive indices, remove older 1 days\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              true,\n\t\t\t\tRollover:             true,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(1)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-archive-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-span-archive-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"rollover archive indices, remove older 0 days, index in write alias cannot be removed\",\n\t\t\tfilter: &IndexFilter{\n\t\t\t\tIndexPrefix:          prefix,\n\t\t\t\tIndexDateSeparator:   \"-\",\n\t\t\t\tArchive:              true,\n\t\t\t\tRollover:             true,\n\t\t\t\tDeleteBeforeThisDate: time20200807.Add(-time.Hour * 24 * time.Duration(0)),\n\t\t\t},\n\t\t\texpected: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        prefix + \"jaeger-span-archive-000001\",\n\t\t\t\t\tCreationTime: time.Date(2020, time.August, 5, 15, 0, 0, 0, time.UTC),\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\tprefix + \"jaeger-span-archive-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tindices := test.filter.Filter(indices)\n\t\t\tassert.Equal(t, test.expected, indices)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/app/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/es-index-cleaner/main.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/featuregate\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-index-cleaner/app\"\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nvar relativeIndexCleaner *featuregate.Gate\n\nfunc init() {\n\trelativeIndexCleaner = featuregate.GlobalRegistry().MustRegister(\n\t\t\"es.index.relativeTimeIndexDeletion\",\n\t\tfeaturegate.StageAlpha,\n\t\tfeaturegate.WithRegisterFromVersion(\"v2.5.0\"),\n\t\tfeaturegate.WithRegisterDescription(\"Controls whether the indices will be deleted relative to the current time or tomorrow midnight.\"),\n\t\tfeaturegate.WithRegisterReferenceURL(\"https://github.com/jaegertracing/jaeger/issues/6236\"),\n\t)\n}\n\nfunc main() {\n\tlogger, _ := zap.NewProduction()\n\tv := viper.New()\n\tcfg := &app.Config{}\n\n\tcommand := &cobra.Command{\n\t\tUse:   \"jaeger-es-index-cleaner NUM_OF_DAYS http://HOSTNAME:PORT\",\n\t\tShort: \"Jaeger es-index-cleaner removes Jaeger indices\",\n\t\tLong:  \"Jaeger es-index-cleaner removes Jaeger indices\",\n\t\tRunE: func(_ *cobra.Command, args []string) error {\n\t\t\tif len(args) != 2 {\n\t\t\t\treturn errors.New(\"wrong number of arguments\")\n\t\t\t}\n\t\t\tnumOfDays, err := strconv.Atoi(args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"could not parse NUM_OF_DAYS argument: %w\", err)\n\t\t\t}\n\n\t\t\tif err := cfg.InitFromViper(v); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to initialize config: %w\", err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\t\t\ttlscfg, err := cfg.TLSConfig.LoadTLSConfig(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error loading tls config : %w\", err)\n\t\t\t}\n\n\t\t\tc := &http.Client{\n\t\t\t\tTimeout: time.Duration(cfg.MasterNodeTimeoutSeconds) * time.Second,\n\t\t\t\tTransport: &http.Transport{\n\t\t\t\t\tProxy:           http.ProxyFromEnvironment,\n\t\t\t\t\tTLSClientConfig: tlscfg,\n\t\t\t\t},\n\t\t\t}\n\t\t\ti := client.IndicesClient{\n\t\t\t\tClient: client.Client{\n\t\t\t\t\tEndpoint:  args[1],\n\t\t\t\t\tClient:    c,\n\t\t\t\t\tBasicAuth: basicAuth(cfg.Username, cfg.Password),\n\t\t\t\t},\n\t\t\t\tMasterTimeoutSeconds:   cfg.MasterNodeTimeoutSeconds,\n\t\t\t\tIgnoreUnavailableIndex: true,\n\t\t\t}\n\n\t\t\tindices, err := i.GetJaegerIndices(cfg.IndexPrefix)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tdeleteIndicesBefore := app.CalculateDeletionCutoff(time.Now().UTC(), numOfDays, relativeIndexCleaner.IsEnabled())\n\t\t\tlogger.Info(\"Indices before this date will be deleted\", zap.String(\"date\", deleteIndicesBefore.Format(time.RFC3339)))\n\n\t\t\tfilter := &app.IndexFilter{\n\t\t\t\tIndexPrefix:          cfg.IndexPrefix,\n\t\t\t\tIndexDateSeparator:   cfg.IndexDateSeparator,\n\t\t\t\tArchive:              cfg.Archive,\n\t\t\t\tRollover:             cfg.Rollover,\n\t\t\t\tDeleteBeforeThisDate: deleteIndicesBefore,\n\t\t\t}\n\t\t\tlogger.Info(\"Queried indices\", zap.Any(\"indices\", indices))\n\t\t\tindices = filter.Filter(indices)\n\n\t\t\tif len(indices) == 0 {\n\t\t\t\tlogger.Info(\"No indices to delete\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tlogger.Info(\"Deleting indices\", zap.Any(\"indices\", indices))\n\t\t\treturn i.DeleteIndices(indices)\n\t\t},\n\t}\n\n\tconfig.AddFlags(\n\t\tv,\n\t\tcommand,\n\t\tcfg.AddFlags,\n\t)\n\n\tcommand.Flags().AddFlagSet(pflag.CommandLine)\n\tif err := command.Execute(); err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n\nfunc basicAuth(username, password string) string {\n\tif username == \"\" || password == \"\" {\n\t\treturn \"\"\n\t}\n\treturn base64.StdEncoding.EncodeToString([]byte(username + \":\" + password))\n}\n"
  },
  {
    "path": "cmd/es-rollover/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nARG base_image\n\nFROM $base_image AS release\nARG TARGETARCH\nARG USER_UID=10001\nCOPY es-rollover-linux-$TARGETARCH /go/bin/es-rollover\nENTRYPOINT [\"/go/bin/es-rollover\"]\nUSER ${USER_UID}\n"
  },
  {
    "path": "cmd/es-rollover/app/actions.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nfunc newESClient(endpoint string, cfg *Config, tlsCfg *tls.Config) client.Client {\n\thttpClient := &http.Client{\n\t\tTimeout: time.Duration(cfg.Timeout) * time.Second,\n\t\tTransport: &http.Transport{\n\t\t\tProxy:           http.ProxyFromEnvironment,\n\t\t\tTLSClientConfig: tlsCfg,\n\t\t},\n\t}\n\treturn client.Client{\n\t\tEndpoint:  endpoint,\n\t\tClient:    httpClient,\n\t\tBasicAuth: client.BasicAuth(cfg.Username, cfg.Password),\n\t}\n}\n\n// Action is an interface that each action (init, rollover and lookback) of the es-rollover should implement\ntype Action interface {\n\tDo() error\n}\n\n// ActionExecuteOptions are the options passed to the execute action function\ntype ActionExecuteOptions struct {\n\tArgs   []string\n\tViper  *viper.Viper\n\tLogger *zap.Logger\n}\n\n// ActionCreatorFunction type is the function type in charge of create the action to be executed\ntype ActionCreatorFunction func(client.Client, Config) Action\n\n// ExecuteAction execute the action returned by the createAction function\nfunc ExecuteAction(opts ActionExecuteOptions, createAction ActionCreatorFunction) error {\n\tcfg := Config{}\n\tif err := cfg.InitFromViper(opts.Viper); err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize config: %w\", err)\n\t}\n\n\tctx := context.Background()\n\ttlsCfg, err := cfg.TLSConfig.LoadTLSConfig(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"TLS configuration failed: %w\", err)\n\t}\n\n\tesClient := newESClient(opts.Args[0], &cfg, tlsCfg)\n\taction := createAction(esClient, cfg)\n\treturn action.Do()\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/actions_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nvar errActionTest = errors.New(\"action error\")\n\ntype dummyAction struct {\n\tTestFn func() error\n}\n\nfunc (a *dummyAction) Do() error {\n\treturn a.TestFn()\n}\n\nfunc TestExecuteAction(t *testing.T) {\n\ttests := []struct {\n\t\tname                  string\n\t\tflags                 []string\n\t\texpectedExecuteAction bool\n\t\texpectedSkip          bool\n\t\texpectedError         error\n\t\tactionFunction        func() error\n\t\tconfigError           bool\n\t}{\n\t\t{\n\t\t\tname: \"execute errored action\",\n\t\t\tflags: []string{\n\t\t\t\t\"--es.tls.skip-host-verify=true\",\n\t\t\t},\n\t\t\texpectedExecuteAction: true,\n\t\t\texpectedSkip:          true,\n\t\t\texpectedError:         errActionTest,\n\t\t},\n\t\t{\n\t\t\tname: \"execute success action\",\n\t\t\tflags: []string{\n\t\t\t\t\"--es.tls.skip-host-verify=true\",\n\t\t\t},\n\t\t\texpectedExecuteAction: true,\n\t\t\texpectedSkip:          true,\n\t\t\texpectedError:         nil,\n\t\t},\n\t\t{\n\t\t\tname: \"don't action because error in tls options\",\n\t\t\tflags: []string{\n\t\t\t\t\"--es.tls.cert=/invalid/path/for/cert\",\n\t\t\t},\n\t\t\texpectedExecuteAction: false,\n\t\t\tconfigError:           true,\n\t\t},\n\t}\n\tlogger := zap.NewNop()\n\targs := []string{\n\t\t\"https://localhost:9300\",\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tv, command := config.Viperize(AddFlags)\n\t\t\tcmdLine := append([]string{\"--es.tls.enabled=true\"}, test.flags...)\n\t\t\trequire.NoError(t, command.ParseFlags(cmdLine))\n\t\t\texecutedAction := false\n\t\t\terr := ExecuteAction(ActionExecuteOptions{\n\t\t\t\tArgs:   args,\n\t\t\t\tViper:  v,\n\t\t\t\tLogger: logger,\n\t\t\t}, func(c client.Client, _ Config) Action {\n\t\t\t\tassert.Equal(t, \"https://localhost:9300\", c.Endpoint)\n\t\t\t\ttransport, ok := c.Client.Transport.(*http.Transport)\n\t\t\t\trequire.True(t, ok)\n\t\t\t\tassert.True(t, transport.TLSClientConfig.InsecureSkipVerify)\n\t\t\t\treturn &dummyAction{\n\t\t\t\t\tTestFn: func() error {\n\t\t\t\t\t\texecutedAction = true\n\t\t\t\t\t\treturn test.expectedError\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\t\t\tassert.Equal(t, test.expectedExecuteAction, executedAction)\n\t\t\tif test.configError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, test.expectedError, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExecuteAction_ConfigError(t *testing.T) {\n\tv, command := config.Viperize(AddFlags)\n\tcmdLine := []string{\n\t\t\"--es.tls.ca=/nonexistent/ca.crt\",\n\t}\n\trequire.NoError(t, command.ParseFlags(cmdLine))\n\tlogger := zap.NewNop()\n\targs := []string{\n\t\t\"https://localhost:9300\",\n\t}\n\n\terr := ExecuteAction(ActionExecuteOptions{\n\t\tArgs:   args,\n\t\tViper:  v,\n\t\tLogger: logger,\n\t}, func(_ client.Client, _ Config) Action {\n\t\treturn &dummyAction{\n\t\t\tTestFn: func() error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t}\n\t})\n\trequire.Error(t, err)\n\tassert.ErrorContains(t, err, \"failed to initialize config\")\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/flags.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config/tlscfg\"\n)\n\nvar tlsFlagsCfg = tlscfg.ClientFlagsConfig{Prefix: \"es\"}\n\nconst (\n\tindexPrefix      = \"index-prefix\"\n\tarchive          = \"archive\"\n\tusername         = \"es.username\"\n\tpassword         = \"es.password\"\n\tuseILM           = \"es.use-ilm\"\n\tilmPolicyName    = \"es.ilm-policy-name\"\n\ttimeout          = \"timeout\"\n\tskipDependencies = \"skip-dependencies\"\n\tadaptiveSampling = \"adaptive-sampling\"\n)\n\n// Config holds the global configurations for the es rollover, common to all actions\ntype Config struct {\n\tIndexPrefix      string\n\tArchive          bool\n\tUsername         string\n\tPassword         string //nolint:gosec // G117\n\tTLSEnabled       bool\n\tILMPolicyName    string\n\tUseILM           bool\n\tTimeout          int\n\tSkipDependencies bool\n\tAdaptiveSampling bool\n\tTLSConfig        configtls.ClientConfig\n}\n\n// AddFlags adds flags\nfunc AddFlags(flags *flag.FlagSet) {\n\tflags.String(indexPrefix, \"\", \"Index prefix\")\n\tflags.Bool(archive, false, \"Handle archive indices\")\n\tflags.String(username, \"\", \"The username required by storage\")\n\tflags.String(password, \"\", \"The password required by storage\")\n\tflags.Bool(useILM, false, \"Use ILM to manage jaeger indices\")\n\tflags.String(ilmPolicyName, \"jaeger-ilm-policy\", \"The name of the ILM policy to use if ILM is active\")\n\tflags.Int(timeout, 120, \"Number of seconds to wait for master node response\")\n\tflags.Bool(skipDependencies, false, \"Disable rollover for dependencies index\")\n\tflags.Bool(adaptiveSampling, false, \"Enable rollover for adaptive sampling index\")\n\ttlsFlagsCfg.AddFlags(flags)\n}\n\n// InitFromViper initializes config from viper.Viper.\nfunc (c *Config) InitFromViper(v *viper.Viper) error {\n\tc.IndexPrefix = v.GetString(indexPrefix)\n\tif c.IndexPrefix != \"\" {\n\t\tc.IndexPrefix += \"-\"\n\t}\n\tc.Archive = v.GetBool(archive)\n\tc.Username = v.GetString(username)\n\tc.Password = v.GetString(password)\n\tc.ILMPolicyName = v.GetString(ilmPolicyName)\n\tc.UseILM = v.GetBool(useILM)\n\tc.Timeout = v.GetInt(timeout)\n\tc.SkipDependencies = v.GetBool(skipDependencies)\n\tc.AdaptiveSampling = v.GetBool(adaptiveSampling)\n\ttlsCfg, err := tlsFlagsCfg.InitFromViper(v)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.TLSConfig = tlsCfg\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/flags_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBindFlags(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tAddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--index-prefix=tenant1\",\n\t\t\"--archive=true\",\n\t\t\"--timeout=150\",\n\t\t\"--es.username=admin\",\n\t\t\"--es.password=qwerty123\",\n\t\t\"--es.use-ilm=true\",\n\t\t\"--es.ilm-policy-name=jaeger-ilm\",\n\t\t\"--skip-dependencies=true\",\n\t\t\"--adaptive-sampling=true\",\n\t})\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, c.InitFromViper(v))\n\tassert.Equal(t, \"tenant1-\", c.IndexPrefix)\n\tassert.True(t, c.Archive)\n\tassert.Equal(t, 150, c.Timeout)\n\tassert.Equal(t, \"admin\", c.Username)\n\tassert.Equal(t, \"qwerty123\", c.Password)\n\tassert.Equal(t, \"jaeger-ilm\", c.ILMPolicyName)\n\tassert.True(t, c.SkipDependencies)\n\tassert.True(t, c.AdaptiveSampling)\n}\n\nfunc TestInitFromViper_TLSError(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tAddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--es.tls.ca=/nonexistent/ca.crt\",\n\t})\n\trequire.NoError(t, err)\n\n\terr = c.InitFromViper(v)\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/index_options.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nconst (\n\twriteAliasFormat    = \"%s-write\"\n\treadAliasFormat     = \"%s-read\"\n\trolloverIndexFormat = \"%s-000001\"\n)\n\n// IndexOption holds the information for the indices to rollover\ntype IndexOption struct {\n\tprefix    string\n\tindexType string\n\tMapping   string\n}\n\n// RolloverIndices return an array of indices to rollover\nfunc RolloverIndices(archive bool, skipDependencies bool, adaptiveSampling bool, prefix string) []IndexOption {\n\tif archive {\n\t\treturn []IndexOption{\n\t\t\t{\n\t\t\t\tprefix:    prefix,\n\t\t\t\tindexType: \"jaeger-span-archive\",\n\t\t\t\tMapping:   \"jaeger-span\",\n\t\t\t},\n\t\t}\n\t}\n\n\tindexOptions := []IndexOption{\n\t\t{\n\t\t\tprefix:    prefix,\n\t\t\tMapping:   \"jaeger-span\",\n\t\t\tindexType: \"jaeger-span\",\n\t\t},\n\t\t{\n\t\t\tprefix:    prefix,\n\t\t\tMapping:   \"jaeger-service\",\n\t\t\tindexType: \"jaeger-service\",\n\t\t},\n\t}\n\n\tif !skipDependencies {\n\t\tindexOptions = append(indexOptions, IndexOption{\n\t\t\tprefix:    prefix,\n\t\t\tMapping:   \"jaeger-dependencies\",\n\t\t\tindexType: \"jaeger-dependencies\",\n\t\t})\n\t}\n\n\tif adaptiveSampling {\n\t\tindexOptions = append(indexOptions, IndexOption{\n\t\t\tprefix:    prefix,\n\t\t\tMapping:   \"jaeger-sampling\",\n\t\t\tindexType: \"jaeger-sampling\",\n\t\t})\n\t}\n\n\treturn indexOptions\n}\n\nfunc (i *IndexOption) IndexName() string {\n\treturn strings.TrimLeft(fmt.Sprintf(\"%s%s\", i.prefix, i.indexType), \"-\")\n}\n\n// ReadAliasName returns read alias name of the index\nfunc (i *IndexOption) ReadAliasName() string {\n\treturn fmt.Sprintf(readAliasFormat, i.IndexName())\n}\n\n// WriteAliasName returns write alias name of the index\nfunc (i *IndexOption) WriteAliasName() string {\n\treturn fmt.Sprintf(writeAliasFormat, i.IndexName())\n}\n\n// InitialRolloverIndex returns the initial index rollover name\nfunc (i *IndexOption) InitialRolloverIndex() string {\n\treturn fmt.Sprintf(rolloverIndexFormat, i.IndexName())\n}\n\n// TemplateName returns the prefixed template name\nfunc (i *IndexOption) TemplateName() string {\n\treturn strings.TrimLeft(fmt.Sprintf(\"%s%s\", i.prefix, i.Mapping), \"-\")\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/index_options_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRolloverIndices(t *testing.T) {\n\ttype expectedValues struct {\n\t\tmapping              string\n\t\ttemplateName         string\n\t\treadAliasName        string\n\t\twriteAliasName       string\n\t\tinitialRolloverIndex string\n\t}\n\n\ttests := []struct {\n\t\tname             string\n\t\tarchive          bool\n\t\tprefix           string\n\t\tskipDependencies bool\n\t\tadaptiveSampling bool\n\t\texpected         []expectedValues\n\t}{\n\t\t{\n\t\t\tname: \"Empty prefix\",\n\t\t\texpected: []expectedValues{\n\t\t\t\t{\n\t\t\t\t\ttemplateName:         \"jaeger-span\",\n\t\t\t\t\tmapping:              \"jaeger-span\",\n\t\t\t\t\treadAliasName:        \"jaeger-span-read\",\n\t\t\t\t\twriteAliasName:       \"jaeger-span-write\",\n\t\t\t\t\tinitialRolloverIndex: \"jaeger-span-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttemplateName:         \"jaeger-service\",\n\t\t\t\t\tmapping:              \"jaeger-service\",\n\t\t\t\t\treadAliasName:        \"jaeger-service-read\",\n\t\t\t\t\twriteAliasName:       \"jaeger-service-write\",\n\t\t\t\t\tinitialRolloverIndex: \"jaeger-service-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttemplateName:         \"jaeger-dependencies\",\n\t\t\t\t\tmapping:              \"jaeger-dependencies\",\n\t\t\t\t\treadAliasName:        \"jaeger-dependencies-read\",\n\t\t\t\t\twriteAliasName:       \"jaeger-dependencies-write\",\n\t\t\t\t\tinitialRolloverIndex: \"jaeger-dependencies-000001\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"archive with prefix\",\n\t\t\tarchive: true,\n\t\t\tprefix:  \"mytenant\",\n\t\t\texpected: []expectedValues{\n\t\t\t\t{\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-span\",\n\t\t\t\t\tmapping:              \"jaeger-span\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-span-archive-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-span-archive-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-span-archive-000001\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"archive empty prefix\",\n\t\t\tarchive: true,\n\t\t\texpected: []expectedValues{\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-span\",\n\t\t\t\t\ttemplateName:         \"jaeger-span\",\n\t\t\t\t\treadAliasName:        \"jaeger-span-archive-read\",\n\t\t\t\t\twriteAliasName:       \"jaeger-span-archive-write\",\n\t\t\t\t\tinitialRolloverIndex: \"jaeger-span-archive-000001\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"with prefix\",\n\t\t\tprefix:           \"mytenant\",\n\t\t\tadaptiveSampling: true,\n\t\t\texpected: []expectedValues{\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-span\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-span\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-span-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-span-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-span-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-service\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-service\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-service-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-service-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-service-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-dependencies\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-dependencies\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-dependencies-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-dependencies-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-dependencies-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-sampling\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-sampling\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-sampling-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-sampling-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-sampling-000001\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"skip-dependency enable\",\n\t\t\tprefix:           \"mytenant\",\n\t\t\tskipDependencies: true,\n\t\t\texpected: []expectedValues{\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-span\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-span\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-span-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-span-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-span-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-service\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-service\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-service-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-service-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-service-000001\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"adaptive sampling enable\",\n\t\t\tprefix:           \"mytenant\",\n\t\t\tskipDependencies: true,\n\t\t\tadaptiveSampling: true,\n\t\t\texpected: []expectedValues{\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-span\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-span\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-span-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-span-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-span-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-service\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-service\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-service-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-service-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-service-000001\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmapping:              \"jaeger-sampling\",\n\t\t\t\t\ttemplateName:         \"mytenant-jaeger-sampling\",\n\t\t\t\t\treadAliasName:        \"mytenant-jaeger-sampling-read\",\n\t\t\t\t\twriteAliasName:       \"mytenant-jaeger-sampling-write\",\n\t\t\t\t\tinitialRolloverIndex: \"mytenant-jaeger-sampling-000001\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tif test.prefix != \"\" {\n\t\t\t\ttest.prefix += \"-\"\n\t\t\t}\n\t\t\tresult := RolloverIndices(test.archive, test.skipDependencies, test.adaptiveSampling, test.prefix)\n\t\t\tassert.Len(t, result, len(test.expected))\n\t\t\tfor i, r := range result {\n\t\t\t\tassert.Equal(t, test.expected[i].templateName, r.TemplateName())\n\t\t\t\tassert.Equal(t, test.expected[i].mapping, r.Mapping)\n\t\t\t\tassert.Equal(t, test.expected[i].readAliasName, r.ReadAliasName())\n\t\t\t\tassert.Equal(t, test.expected[i].writeAliasName, r.WriteAliasName())\n\t\t\t\tassert.Equal(t, test.expected[i].initialRolloverIndex, r.InitialRolloverIndex())\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/init/action.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage init\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/filter\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/mappings\"\n)\n\nconst ilmVersionSupport = 7\n\n// Action holds the configuration and clients for init action\ntype Action struct {\n\tConfig        Config\n\tClusterClient client.ClusterAPI\n\tIndicesClient client.IndexAPI\n\tILMClient     client.IndexManagementLifecycleAPI\n}\n\nfunc (c Action) getMapping(version uint, mappingType mappings.MappingType) (string, error) {\n\tc.Config.Indices.IndexPrefix = config.IndexPrefix(c.Config.Config.IndexPrefix)\n\tmappingBuilder := mappings.MappingBuilder{\n\t\tTemplateBuilder: es.TextTemplateBuilder{},\n\t\tIndices:         c.Config.Indices,\n\t\tUseILM:          c.Config.UseILM,\n\t\tILMPolicyName:   c.Config.ILMPolicyName,\n\t\tEsVersion:       version,\n\t}\n\n\treturn mappingBuilder.GetMapping(mappingType)\n}\n\n// Do the init action\nfunc (c Action) Do() error {\n\tversion, err := c.ClusterClient.Version()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Config.UseILM {\n\t\tif version < ilmVersionSupport {\n\t\t\treturn errors.New(\"ILM is supported only for ES version 7+\")\n\t\t}\n\t\tpolicyExist, err := c.ILMClient.Exists(c.Config.ILMPolicyName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !policyExist {\n\t\t\treturn fmt.Errorf(\"ILM policy %s doesn't exist in Elasticsearch. Please create it and re-run init\", c.Config.ILMPolicyName)\n\t\t}\n\t}\n\trolloverIndices := app.RolloverIndices(c.Config.Archive, c.Config.SkipDependencies, c.Config.AdaptiveSampling, c.Config.Config.IndexPrefix)\n\tfor _, indexName := range rolloverIndices {\n\t\tif err := c.init(version, indexName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc createIndexIfNotExist(c client.IndexAPI, index string) error {\n\texists, err := c.IndexExists(index)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif exists {\n\t\treturn nil\n\t}\n\taliasExists, err := c.AliasExists(index)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif aliasExists {\n\t\treturn nil\n\t}\n\treturn c.CreateIndex(index)\n}\n\nfunc (c Action) init(version uint, indexopt app.IndexOption) error {\n\tmappingType, err := mappings.MappingTypeFromString(indexopt.Mapping)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmapping, err := c.getMapping(version, mappingType)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = c.IndicesClient.CreateTemplate(mapping, indexopt.TemplateName())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tindex := indexopt.InitialRolloverIndex()\n\terr = createIndexIfNotExist(c.IndicesClient, index)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tjaegerIndices, err := c.IndicesClient.GetJaegerIndices(c.Config.Config.IndexPrefix)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treadAlias := indexopt.ReadAliasName()\n\twriteAlias := indexopt.WriteAliasName()\n\taliases := []client.Alias{}\n\n\tif !filter.AliasExists(jaegerIndices, readAlias) {\n\t\taliases = append(aliases, client.Alias{\n\t\t\tIndex:        index,\n\t\t\tName:         readAlias,\n\t\t\tIsWriteIndex: false,\n\t\t})\n\t}\n\n\tif !filter.AliasExists(jaegerIndices, writeAlias) {\n\t\taliases = append(aliases, client.Alias{\n\t\t\tIndex:        index,\n\t\t\tName:         writeAlias,\n\t\t\tIsWriteIndex: c.Config.UseILM,\n\t\t})\n\t}\n\n\tif len(aliases) > 0 {\n\t\terr = c.IndicesClient.CreateAlias(aliases)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/init/action_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage init\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client/mocks\"\n)\n\nfunc applyTestDefaults(cfg *Config) {\n\t// Set defaults only if missing\n\tif cfg.Indices.Spans.Shards == 0 {\n\t\tcfg.Indices.Spans.Shards = 3\n\t}\n\tif cfg.Indices.Spans.Replicas == nil {\n\t\tcfg.Indices.Spans.Replicas = new(int64(1))\n\t}\n\tif cfg.Indices.Spans.Priority == 0 {\n\t\tcfg.Indices.Spans.Priority = 10\n\t}\n}\n\nfunc TestIndexCreateIfNotExist(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tindexExists    bool\n\t\tindexExistsErr error\n\t\taliasExists    bool\n\t\taliasExistsErr error\n\t\tcreateIndexErr error\n\t\texpectedError  string\n\t}{\n\t\t{\n\t\t\tname:        \"success when index exists\",\n\t\t\tindexExists: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"generic error from IndexExists\",\n\t\t\tindexExistsErr: errors.New(\"may be an http error from index exists\"),\n\t\t\texpectedError:  \"may be an http error from index exists\",\n\t\t},\n\t\t{\n\t\t\tname:        \"success when alias exists\",\n\t\t\taliasExists: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"generic error from AliasExists\",\n\t\t\taliasExistsErr: errors.New(\"may be an http error from alias exists\"),\n\t\t\texpectedError:  \"may be an http error from alias exists\",\n\t\t},\n\t\t{\n\t\t\tname:           \"generic error from create index\",\n\t\t\tcreateIndexErr: errors.New(\"may be an http error from create index\"),\n\t\t\texpectedError:  \"may be an http error from create index\",\n\t\t},\n\t\t{\n\t\t\tname: \"success when index and alias does not exist\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tindexClient := &mocks.IndexAPI{}\n\t\t\tindexClient.On(\"IndexExists\", \"jaeger-span\").Return(test.indexExists, test.indexExistsErr)\n\t\t\tindexClient.On(\"AliasExists\", \"jaeger-span\").Return(test.aliasExists, test.aliasExistsErr)\n\t\t\tindexClient.On(\"CreateIndex\", \"jaeger-span\").Return(test.createIndexErr)\n\t\t\terr := createIndexIfNotExist(indexClient, \"jaeger-span\")\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\tassert.EqualError(t, err, test.expectedError)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRolloverAction(t *testing.T) {\n\ttests := []struct {\n\t\tname                  string\n\t\tsetupCallExpectations func(indexClient *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, ilmClient *mocks.IndexManagementLifecycleAPI)\n\t\tconfig                Config\n\t\texpectedErr           error\n\t}{\n\t\t{\n\t\t\tname: \"Unsupported version\",\n\t\t\tsetupCallExpectations: func(_ *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, _ *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(5), nil)\n\t\t\t},\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  true,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"ILM is supported only for ES version 7+\"),\n\t\t},\n\t\t{\n\t\t\tname: \"error getting version\",\n\t\t\tsetupCallExpectations: func(_ *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, _ *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(0), errors.New(\"version error\"))\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"version error\"),\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"ilm doesnt exist\",\n\t\t\tsetupCallExpectations: func(_ *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, ilmClient *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tilmClient.On(\"Exists\", \"myilmpolicy\").Return(false, nil)\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"ILM policy myilmpolicy doesn't exist in Elasticsearch. Please create it and re-run init\"),\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive:       true,\n\t\t\t\t\tUseILM:        true,\n\t\t\t\t\tILMPolicyName: \"myilmpolicy\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"fail get ilm policy\",\n\t\t\tsetupCallExpectations: func(_ *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, ilmClient *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tilmClient.On(\"Exists\", \"myilmpolicy\").Return(false, errors.New(\"error getting ilm policy\"))\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"error getting ilm policy\"),\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive:       true,\n\t\t\t\t\tUseILM:        true,\n\t\t\t\t\tILMPolicyName: \"myilmpolicy\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"fail to create template\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, _ *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tindexClient.On(\"CreateTemplate\", mock.Anything, \"jaeger-span\").Return(errors.New(\"error creating template\"))\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"error creating template\"),\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"fail to get jaeger indices\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, _ *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tindexClient.On(\"IndexExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"AliasExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"CreateTemplate\", mock.Anything, \"jaeger-span\").Return(nil)\n\t\t\t\tindexClient.On(\"CreateIndex\", \"jaeger-span-archive-000001\").Return(nil)\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return([]client.Index{}, errors.New(\"error getting jaeger indices\"))\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"error getting jaeger indices\"),\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"fail to create alias\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, _ *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tindexClient.On(\"IndexExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"AliasExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"CreateTemplate\", mock.Anything, \"jaeger-span\").Return(nil)\n\t\t\t\tindexClient.On(\"CreateIndex\", \"jaeger-span-archive-000001\").Return(nil)\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return([]client.Index{}, nil)\n\t\t\t\tindexClient.On(\"CreateAlias\", []client.Alias{\n\t\t\t\t\t{Index: \"jaeger-span-archive-000001\", Name: \"jaeger-span-archive-read\", IsWriteIndex: false},\n\t\t\t\t\t{Index: \"jaeger-span-archive-000001\", Name: \"jaeger-span-archive-write\", IsWriteIndex: false},\n\t\t\t\t}).Return(errors.New(\"error creating aliases\"))\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"error creating aliases\"),\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"create rollover index\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, _ *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tindexClient.On(\"IndexExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"AliasExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"CreateTemplate\", mock.Anything, \"jaeger-span\").Return(nil)\n\t\t\t\tindexClient.On(\"CreateIndex\", \"jaeger-span-archive-000001\").Return(nil)\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return([]client.Index{}, nil)\n\t\t\t\tindexClient.On(\"CreateAlias\", []client.Alias{\n\t\t\t\t\t{Index: \"jaeger-span-archive-000001\", Name: \"jaeger-span-archive-read\", IsWriteIndex: false},\n\t\t\t\t\t{Index: \"jaeger-span-archive-000001\", Name: \"jaeger-span-archive-write\", IsWriteIndex: false},\n\t\t\t\t}).Return(nil)\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"create rollover index with ilm\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, clusterClient *mocks.ClusterAPI, ilmClient *mocks.IndexManagementLifecycleAPI) {\n\t\t\t\tclusterClient.On(\"Version\").Return(uint(7), nil)\n\t\t\t\tindexClient.On(\"IndexExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"AliasExists\", \"jaeger-span-archive-000001\").Return(false, nil)\n\t\t\t\tindexClient.On(\"CreateTemplate\", mock.Anything, \"jaeger-span\").Return(nil)\n\t\t\t\tindexClient.On(\"CreateIndex\", \"jaeger-span-archive-000001\").Return(nil)\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return([]client.Index{}, nil)\n\t\t\t\tilmClient.On(\"Exists\", \"jaeger-ilm\").Return(true, nil)\n\t\t\t\tindexClient.On(\"CreateAlias\", []client.Alias{\n\t\t\t\t\t{Index: \"jaeger-span-archive-000001\", Name: \"jaeger-span-archive-read\", IsWriteIndex: false},\n\t\t\t\t\t{Index: \"jaeger-span-archive-000001\", Name: \"jaeger-span-archive-write\", IsWriteIndex: true},\n\t\t\t\t}).Return(nil)\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t\tconfig: Config{\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive:       true,\n\t\t\t\t\tUseILM:        true,\n\t\t\t\t\tILMPolicyName: \"jaeger-ilm\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// Apply local test defaults\n\t\t\tapplyTestDefaults(&test.config)\n\t\t\tindexClient := &mocks.IndexAPI{}\n\t\t\tclusterClient := &mocks.ClusterAPI{}\n\t\t\tilmClient := &mocks.IndexManagementLifecycleAPI{}\n\t\t\tinitAction := Action{\n\t\t\t\tConfig:        test.config,\n\t\t\t\tIndicesClient: indexClient,\n\t\t\t\tClusterClient: clusterClient,\n\t\t\t\tILMClient:     ilmClient,\n\t\t\t}\n\n\t\t\ttest.setupCallExpectations(indexClient, clusterClient, ilmClient)\n\n\t\t\terr := initAction.Do()\n\t\t\tif test.expectedErr != nil {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Equal(t, test.expectedErr, err)\n\t\t\t}\n\n\t\t\tindexClient.AssertExpectations(t)\n\t\t\tclusterClient.AssertExpectations(t)\n\t\t\tilmClient.AssertExpectations(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/init/flags.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage init\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\tcfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n)\n\nconst (\n\tshards                       = \"shards\"\n\treplicas                     = \"replicas\"\n\tprioritySpanTemplate         = \"priority-span-template\"\n\tpriorityServiceTemplate      = \"priority-service-template\"\n\tpriorityDependenciesTemplate = \"priority-dependencies-template\"\n\tprioritySamplingTemplate     = \"priority-sampling-template\"\n)\n\n// Config holds configuration for index cleaner binary.\n// Config.IndexPrefix supersedes Indices.IndexPrefix\ntype Config struct {\n\tapp.Config\n\tcfg.Indices\n}\n\n// AddFlags adds flags for TLS to the FlagSet.\nfunc (*Config) AddFlags(flags *flag.FlagSet) {\n\tflags.Int(shards, 5, \"Number of shards\")\n\tflags.Int(replicas, 1, \"Number of replicas\")\n\tflags.Int(prioritySpanTemplate, 0, \"Priority of jaeger-span index template (ESv8 only)\")\n\tflags.Int(priorityServiceTemplate, 0, \"Priority of jaeger-service index template (ESv8 only)\")\n\tflags.Int(priorityDependenciesTemplate, 0, \"Priority of jaeger-dependencies index template (ESv8 only)\")\n\tflags.Int(prioritySamplingTemplate, 0, \"Priority of jaeger-sampling index template (ESv8 only)\")\n}\n\n// InitFromViper initializes config from viper.Viper.\nfunc (c *Config) InitFromViper(v *viper.Viper) {\n\tc.Indices.Spans.Shards = v.GetInt64(shards)\n\tc.Indices.Services.Shards = v.GetInt64(shards)\n\tc.Indices.Dependencies.Shards = v.GetInt64(shards)\n\tc.Indices.Sampling.Shards = v.GetInt64(shards)\n\n\trepsPtr := new(v.GetInt64(replicas))\n\tc.Indices.Spans.Replicas = repsPtr\n\tc.Indices.Services.Replicas = repsPtr\n\tc.Indices.Dependencies.Replicas = repsPtr\n\tc.Indices.Sampling.Replicas = repsPtr\n\n\tc.Indices.Spans.Priority = v.GetInt64(prioritySpanTemplate)\n\tc.Indices.Services.Priority = v.GetInt64(priorityServiceTemplate)\n\tc.Indices.Dependencies.Priority = v.GetInt64(priorityDependenciesTemplate)\n\tc.Indices.Sampling.Priority = v.GetInt64(prioritySamplingTemplate)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/init/flags_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage init\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBindFlags(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tc.AddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--shards=8\",\n\t\t\"--replicas=16\",\n\t\t\"--priority-span-template=300\",\n\t\t\"--priority-service-template=301\",\n\t\t\"--priority-dependencies-template=302\",\n\t\t\"--priority-sampling-template=303\",\n\t})\n\trequire.NoError(t, err)\n\n\tc.InitFromViper(v)\n\tassert.EqualValues(t, 8, c.Indices.Spans.Shards)\n\trequire.NotNil(t, c.Indices.Spans.Replicas)\n\tassert.EqualValues(t, 16, *c.Indices.Spans.Replicas)\n\tassert.EqualValues(t, 300, c.Indices.Spans.Priority)\n\tassert.EqualValues(t, 301, c.Indices.Services.Priority)\n\tassert.EqualValues(t, 302, c.Indices.Dependencies.Priority)\n\tassert.EqualValues(t, 303, c.Indices.Sampling.Priority)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/init/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage init\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/action.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport (\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/filter\"\n)\n\nvar timeNow func() time.Time = time.Now\n\n// Action holds the configuration and clients for lookback action\ntype Action struct {\n\tConfig\n\tIndicesClient client.IndexAPI\n\tLogger        *zap.Logger\n}\n\n// Do the lookback action\nfunc (a *Action) Do() error {\n\trolloverIndices := app.RolloverIndices(a.Config.Archive, a.Config.SkipDependencies, a.Config.AdaptiveSampling, a.Config.IndexPrefix)\n\tfor _, indexName := range rolloverIndices {\n\t\tif err := a.lookback(indexName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *Action) lookback(indexSet app.IndexOption) error {\n\tjaegerIndex, err := a.IndicesClient.GetJaegerIndices(a.Config.IndexPrefix)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treadAliasName := indexSet.ReadAliasName()\n\treadAliasIndices := filter.ByAlias(jaegerIndex, []string{readAliasName})\n\texcludedWriteIndex := filter.ByAliasExclude(readAliasIndices, []string{indexSet.WriteAliasName()})\n\tfinalIndices := filter.ByDate(excludedWriteIndex, getTimeReference(timeNow(), a.Unit, a.UnitCount))\n\n\tif len(finalIndices) == 0 {\n\t\ta.Logger.Info(\"No indices to remove from alias\", zap.String(\"readAliasName\", readAliasName))\n\t\treturn nil\n\t}\n\n\taliases := make([]client.Alias, 0, len(finalIndices))\n\ta.Logger.Info(\"About to remove indices\", zap.String(\"readAliasName\", readAliasName), zap.Int(\"indicesCount\", len(finalIndices)))\n\n\tfor _, index := range finalIndices {\n\t\taliases = append(aliases, client.Alias{\n\t\t\tIndex: index.Index,\n\t\t\tName:  readAliasName,\n\t\t})\n\t\ta.Logger.Info(\"To be removed\", zap.String(\"index\", index.Index), zap.String(\"creationTime\", index.CreationTime.String()))\n\t}\n\n\treturn a.IndicesClient.DeleteAlias(aliases)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/action_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client/mocks\"\n)\n\nfunc TestLookBackAction(t *testing.T) {\n\tnowTime := time.Date(2021, 10, 12, 10, 10, 10, 10, time.Local)\n\tindices := []client.Index{\n\t\t{\n\t\t\tIndex: \"jaeger-span-archive-0000\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-archive-other-alias\": true,\n\t\t\t},\n\t\t\tCreationTime: time.Date(2021, 10, 10, 10, 10, 10, 10, time.Local),\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-archive-0001\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-archive-read\": true,\n\t\t\t},\n\t\t\tCreationTime: time.Date(2021, 10, 10, 10, 10, 10, 10, time.Local),\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-archive-0002\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-archive-read\":  true,\n\t\t\t\t\"jaeger-span-archive-write\": true,\n\t\t\t},\n\t\t\tCreationTime: time.Date(2021, 10, 11, 10, 10, 10, 10, time.Local),\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-archive-0002\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-archive-read\": true,\n\t\t\t},\n\t\t\tCreationTime: nowTime,\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-archive-0004\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-archive-read\":  true,\n\t\t\t\t\"jaeger-span-archive-write\": true,\n\t\t\t},\n\t\t\tCreationTime: nowTime,\n\t\t},\n\t}\n\n\ttimeNow = func() time.Time {\n\t\treturn nowTime\n\t}\n\n\ttests := []struct {\n\t\tname                  string\n\t\tsetupCallExpectations func(indexClient *mocks.IndexAPI)\n\t\tconfig                Config\n\t\texpectedErr           error\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI) {\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return(indices, nil)\n\t\t\t\tindexClient.On(\"DeleteAlias\", []client.Alias{\n\t\t\t\t\t{\n\t\t\t\t\t\tIndex: \"jaeger-span-archive-0001\",\n\t\t\t\t\t\tName:  \"jaeger-span-archive-read\",\n\t\t\t\t\t},\n\t\t\t\t}).Return(nil)\n\t\t\t},\n\t\t\tconfig: Config{\n\t\t\t\tUnit:      \"days\",\n\t\t\t\tUnitCount: 1,\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  true,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"get indices error\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI) {\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return(indices, errors.New(\"get indices error\"))\n\t\t\t},\n\t\t\tconfig: Config{\n\t\t\t\tUnit:      \"days\",\n\t\t\t\tUnitCount: 1,\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  true,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"get indices error\"),\n\t\t},\n\t\t{\n\t\t\tname: \"empty indices\",\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI) {\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return([]client.Index{}, nil)\n\t\t\t},\n\t\t\tconfig: Config{\n\t\t\t\tUnit:      \"days\",\n\t\t\t\tUnitCount: 1,\n\t\t\t\tConfig: app.Config{\n\t\t\t\t\tArchive: true,\n\t\t\t\t\tUseILM:  true,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t},\n\t}\n\n\tlogger, _ := zap.NewProduction()\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tindexClient := &mocks.IndexAPI{}\n\t\t\tlookbackAction := Action{\n\t\t\t\tConfig:        test.config,\n\t\t\t\tIndicesClient: indexClient,\n\t\t\t\tLogger:        logger,\n\t\t\t}\n\n\t\t\ttest.setupCallExpectations(indexClient)\n\n\t\t\terr := lookbackAction.Do()\n\t\t\tif test.expectedErr != nil {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Equal(t, test.expectedErr, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/flags.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n)\n\nconst (\n\tunit             = \"unit\"\n\tunitCount        = \"unit-count\"\n\tdefaultUnit      = \"days\"\n\tdefaultUnitCount = 1\n)\n\n// Config holds configuration for index cleaner binary.\ntype Config struct {\n\tapp.Config\n\tUnit      string\n\tUnitCount int\n}\n\n// AddFlags adds flags for TLS to the FlagSet.\nfunc (*Config) AddFlags(flags *flag.FlagSet) {\n\tflags.String(unit, defaultUnit, \"used with lookback to remove indices from read alias e.g, days, weeks, months, years\")\n\tflags.Int(unitCount, defaultUnitCount, \"count of UNITs\")\n}\n\n// InitFromViper initializes config from viper.Viper.\nfunc (c *Config) InitFromViper(v *viper.Viper) {\n\tc.Unit = v.GetString(unit)\n\tc.UnitCount = v.GetInt(unitCount)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/flags_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBindFlags(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tc.AddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--unit=days\",\n\t\t\"--unit-count=16\",\n\t})\n\trequire.NoError(t, err)\n\n\tc.InitFromViper(v)\n\tassert.Equal(t, \"days\", c.Unit)\n\tassert.Equal(t, 16, c.UnitCount)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/time_reference.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport \"time\"\n\nfunc getTimeReference(currentTime time.Time, units string, unitCount int) time.Time {\n\tswitch units {\n\tcase \"minutes\":\n\t\treturn currentTime.Truncate(time.Minute).Add(-time.Duration(unitCount) * time.Minute)\n\tcase \"hours\":\n\t\treturn currentTime.Truncate(time.Hour).Add(-time.Duration(unitCount) * time.Hour)\n\tcase \"days\":\n\t\tyear, month, day := currentTime.Date()\n\t\ttomorrowMidnight := time.Date(year, month, day, 0, 0, 0, 0, currentTime.Location()).AddDate(0, 0, 1)\n\t\treturn tomorrowMidnight.Add(-time.Hour * 24 * time.Duration(unitCount))\n\tcase \"weeks\":\n\t\tyear, month, day := currentTime.Date()\n\t\ttomorrowMidnight := time.Date(year, month, day, 0, 0, 0, 0, currentTime.Location()).AddDate(0, 0, 1)\n\t\treturn tomorrowMidnight.Add(-time.Hour * 24 * time.Duration(7*unitCount))\n\tcase \"months\":\n\t\tyear, month, day := currentTime.Date()\n\t\treturn time.Date(year, month, day, 0, 0, 0, 0, currentTime.Location()).AddDate(0, -1*unitCount, 0)\n\tcase \"years\":\n\t\tyear, month, day := currentTime.Date()\n\t\treturn time.Date(year, month, day, 0, 0, 0, 0, currentTime.Location()).AddDate(-1*unitCount, 0, 0)\n\tdefault:\n\t\treturn currentTime.Truncate(time.Second).Add(-time.Duration(unitCount) * time.Second)\n\t}\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/lookback/time_reference_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage lookback\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetTimeReference(t *testing.T) {\n\tnow := time.Date(2021, time.October, 10, 10, 10, 10, 10, time.UTC)\n\n\ttests := []struct {\n\t\tname         string\n\t\tunit         string\n\t\tunitCount    int\n\t\texpectedTime time.Time\n\t}{\n\t\t{\n\t\t\tname:         \"seconds unit\",\n\t\t\tunit:         \"seconds\",\n\t\t\tunitCount:    30,\n\t\t\texpectedTime: time.Date(2021, time.October, 10, 10, 9, 40, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:         \"minutes unit\",\n\t\t\tunit:         \"minutes\",\n\t\t\tunitCount:    30,\n\t\t\texpectedTime: time.Date(2021, time.October, 10, 9, 40, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:         \"hours unit\",\n\t\t\tunit:         \"hours\",\n\t\t\tunitCount:    2,\n\t\t\texpectedTime: time.Date(2021, time.October, 10, 8, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:         \"days unit\",\n\t\t\tunit:         \"days\",\n\t\t\tunitCount:    2,\n\t\t\texpectedTime: time.Date(2021, 10, 9, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:         \"weeks unit\",\n\t\t\tunit:         \"weeks\",\n\t\t\tunitCount:    2,\n\t\t\texpectedTime: time.Date(2021, time.September, 27, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:         \"months unit\",\n\t\t\tunit:         \"months\",\n\t\t\tunitCount:    2,\n\t\t\texpectedTime: time.Date(2021, time.August, 10, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:         \"years unit\",\n\t\t\tunit:         \"years\",\n\t\t\tunitCount:    2,\n\t\t\texpectedTime: time.Date(2019, time.October, 10, 0, 0, 0, 0, time.UTC),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tref := getTimeReference(now, test.unit, test.unitCount)\n\t\t\tassert.Equal(t, test.expectedTime, ref)\n\t\t})\n\t}\n}\n\nfunc TestGetTimeReference_DefaultCase(t *testing.T) {\n\tnow := time.Date(2021, time.October, 10, 10, 10, 10, 10, time.UTC)\n\n\tunknownUnit := \"unknown-unit\"\n\tunitCount := 30\n\n\tref := getTimeReference(now, unknownUnit, unitCount)\n\n\texpectedTime := time.Date(2021, time.October, 10, 10, 9, 40, 0, time.UTC)\n\tassert.Equal(t, expectedTime, ref)\n\n\tanotherUnknownUnit := \"milliseconds\"\n\tref2 := getTimeReference(now, anotherUnknownUnit, unitCount)\n\n\tassert.Equal(t, expectedTime, ref2)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/rollover/action.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rollover\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/filter\"\n)\n\n// Action holds the configuration and clients for rollover action\ntype Action struct {\n\tConfig\n\tIndicesClient client.IndexAPI\n}\n\n// Do the rollover action\nfunc (a *Action) Do() error {\n\trolloverIndices := app.RolloverIndices(a.Config.Archive, a.Config.SkipDependencies, a.Config.AdaptiveSampling, a.Config.IndexPrefix)\n\tfor _, indexName := range rolloverIndices {\n\t\tif err := a.rollover(indexName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *Action) rollover(indexSet app.IndexOption) error {\n\tconditionsMap := map[string]any{}\n\tif a.Conditions != \"\" {\n\t\terr := json.Unmarshal([]byte(a.Config.Conditions), &conditionsMap)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\twriteAlias := indexSet.WriteAliasName()\n\treadAlias := indexSet.ReadAliasName()\n\terr := a.IndicesClient.Rollover(writeAlias, conditionsMap)\n\tif err != nil {\n\t\treturn err\n\t}\n\tjaegerIndex, err := a.IndicesClient.GetJaegerIndices(a.Config.IndexPrefix)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tindicesWithWriteAlias := filter.ByAlias(jaegerIndex, []string{writeAlias})\n\taliases := make([]client.Alias, 0, len(indicesWithWriteAlias))\n\tfor _, index := range indicesWithWriteAlias {\n\t\taliases = append(aliases, client.Alias{\n\t\t\tIndex: index.Index,\n\t\t\tName:  readAlias,\n\t\t})\n\t}\n\tif len(aliases) == 0 {\n\t\treturn nil\n\t}\n\treturn a.IndicesClient.CreateAlias(aliases)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/rollover/action_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rollover\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client/mocks\"\n)\n\nfunc TestRolloverAction(t *testing.T) {\n\treadIndices := []client.Index{\n\t\t{\n\t\t\tIndex: \"jaeger-read-span\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-archive-write\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\taliasToCreate := []client.Alias{{Index: \"jaeger-read-span\", Name: \"jaeger-span-archive-read\", IsWriteIndex: false}}\n\ttype testCase struct {\n\t\tname                  string\n\t\tconditions            string\n\t\tunmarshalErrExpected  bool\n\t\tgetJaegerIndicesErr   error\n\t\trolloverErr           error\n\t\tcreateAliasErr        error\n\t\texpectedError         bool\n\t\tindices               []client.Index\n\t\tsetupCallExpectations func(indexClient *mocks.IndexAPI, t *testCase)\n\t}\n\n\ttests := []testCase{\n\t\t{\n\t\t\tname:          \"success\",\n\t\t\tconditions:    \"{\\\"max_age\\\": \\\"2d\\\"}\",\n\t\t\texpectedError: false,\n\t\t\tindices:       readIndices,\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, test *testCase) {\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return(test.indices, test.getJaegerIndicesErr)\n\t\t\t\tindexClient.On(\"CreateAlias\", aliasToCreate).Return(test.createAliasErr)\n\t\t\t\tindexClient.On(\"Rollover\", \"jaeger-span-archive-write\", map[string]any{\"max_age\": \"2d\"}).Return(test.rolloverErr)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"no alias write alias\",\n\t\t\tconditions:    \"{\\\"max_age\\\": \\\"2d\\\"}\",\n\t\t\texpectedError: false,\n\t\t\tindices: []client.Index{\n\t\t\t\t{\n\t\t\t\t\tIndex: \"jaeger-read-span\",\n\t\t\t\t\tAliases: map[string]bool{\n\t\t\t\t\t\t\"jaeger-span-archive-read\": true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, test *testCase) {\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return(test.indices, test.getJaegerIndicesErr)\n\t\t\t\tindexClient.On(\"Rollover\", \"jaeger-span-archive-write\", map[string]any{\"max_age\": \"2d\"}).Return(test.rolloverErr)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                \"get jaeger indices error\",\n\t\t\tconditions:          \"{\\\"max_age\\\": \\\"2d\\\"}\",\n\t\t\texpectedError:       true,\n\t\t\tgetJaegerIndicesErr: errors.New(\"unable to get indices\"),\n\t\t\tindices:             readIndices,\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, test *testCase) {\n\t\t\t\tindexClient.On(\"Rollover\", \"jaeger-span-archive-write\", map[string]any{\"max_age\": \"2d\"}).Return(test.rolloverErr)\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return(test.indices, test.getJaegerIndicesErr)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"rollover error\",\n\t\t\tconditions:    \"{\\\"max_age\\\": \\\"2d\\\"}\",\n\t\t\texpectedError: true,\n\t\t\trolloverErr:   errors.New(\"unable to rollover\"),\n\t\t\tindices:       readIndices,\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, test *testCase) {\n\t\t\t\tindexClient.On(\"Rollover\", \"jaeger-span-archive-write\", map[string]any{\"max_age\": \"2d\"}).Return(test.rolloverErr)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:           \"create alias error\",\n\t\t\tconditions:     \"{\\\"max_age\\\": \\\"2d\\\"}\",\n\t\t\texpectedError:  true,\n\t\t\tcreateAliasErr: errors.New(\"unable to create alias\"),\n\t\t\tindices:        readIndices,\n\t\t\tsetupCallExpectations: func(indexClient *mocks.IndexAPI, test *testCase) {\n\t\t\t\tindexClient.On(\"GetJaegerIndices\", \"\").Return(test.indices, test.getJaegerIndicesErr)\n\t\t\t\tindexClient.On(\"CreateAlias\", aliasToCreate).Return(test.createAliasErr)\n\t\t\t\tindexClient.On(\"Rollover\", \"jaeger-span-archive-write\", map[string]any{\"max_age\": \"2d\"}).Return(test.rolloverErr)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                  \"unmarshal conditions error\",\n\t\t\tconditions:            \"{\\\"max_age\\\" \\\"2d\\\"},\",\n\t\t\tunmarshalErrExpected:  true,\n\t\t\tcreateAliasErr:        errors.New(\"unable to create alias\"),\n\t\t\tindices:               readIndices,\n\t\t\tsetupCallExpectations: func(_ *mocks.IndexAPI, _ *testCase) {},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tindexClient := &mocks.IndexAPI{}\n\n\t\t\trolloverAction := Action{\n\t\t\t\tConfig: Config{\n\t\t\t\t\tConditions: test.conditions,\n\t\t\t\t\tConfig: app.Config{\n\t\t\t\t\t\tArchive: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tIndicesClient: indexClient,\n\t\t\t}\n\t\t\ttest.setupCallExpectations(indexClient, &test)\n\t\t\terr := rolloverAction.Do()\n\t\t\tif test.expectedError || test.unmarshalErrExpected {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tindexClient.AssertExpectations(t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/rollover/flags.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rollover\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n)\n\nconst (\n\tconditions               = \"conditions\"\n\tdefaultRollbackCondition = \"{\\\"max_age\\\": \\\"2d\\\"}\"\n)\n\n// Config holds configuration for index cleaner binary.\ntype Config struct {\n\tapp.Config\n\tConditions string\n}\n\n// AddFlags adds flags for TLS to the FlagSet.\nfunc (*Config) AddFlags(flags *flag.FlagSet) {\n\tflags.String(conditions, defaultRollbackCondition, \"conditions used to rollover to a new write index\")\n}\n\n// InitFromViper initializes config from viper.Viper.\nfunc (c *Config) InitFromViper(v *viper.Viper) {\n\tc.Conditions = v.GetString(conditions)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/rollover/flags_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rollover\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBindFlags(t *testing.T) {\n\tv := viper.New()\n\tc := &Config{}\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tc.AddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\terr := command.ParseFlags([]string{\n\t\t\"--conditions={\\\"max_age\\\": \\\"20000d\\\"}\",\n\t})\n\trequire.NoError(t, err)\n\n\tc.InitFromViper(v)\n\tassert.JSONEq(t, `{\"max_age\": \"20000d\"}`, c.Conditions)\n}\n"
  },
  {
    "path": "cmd/es-rollover/app/rollover/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rollover\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/es-rollover/main.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app\"\n\tinitialize \"github.com/jaegertracing/jaeger/cmd/es-rollover/app/init\"\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app/lookback\"\n\t\"github.com/jaegertracing/jaeger/cmd/es-rollover/app/rollover\"\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nfunc main() {\n\tv := viper.New()\n\tlogger, _ := zap.NewProduction()\n\n\trootCmd := &cobra.Command{\n\t\tUse:   \"jaeger-es-rollover\",\n\t\tShort: \"Jaeger es-rollover manages Jaeger indices\",\n\t\tLong:  \"Jaeger es-rollover manages Jaeger indices\",\n\t}\n\n\t// Init command\n\tinitCfg := &initialize.Config{}\n\tinitCommand := &cobra.Command{\n\t\tUse:          \"init http://HOSTNAME:PORT\",\n\t\tShort:        \"creates indices and aliases\",\n\t\tLong:         \"creates indices and aliases\",\n\t\tArgs:         cobra.ExactArgs(1),\n\t\tSilenceUsage: true,\n\t\tRunE: func(_ *cobra.Command, args []string) error {\n\t\t\treturn app.ExecuteAction(app.ActionExecuteOptions{\n\t\t\t\tArgs:   args,\n\t\t\t\tViper:  v,\n\t\t\t\tLogger: logger,\n\t\t\t}, func(c client.Client, cfg app.Config) app.Action {\n\t\t\t\tinitCfg.Config = cfg\n\t\t\t\tinitCfg.InitFromViper(v)\n\t\t\t\tindicesClient := &client.IndicesClient{\n\t\t\t\t\tClient:               c,\n\t\t\t\t\tMasterTimeoutSeconds: initCfg.Timeout,\n\t\t\t\t}\n\t\t\t\tclusterClient := &client.ClusterClient{\n\t\t\t\t\tClient: c,\n\t\t\t\t}\n\t\t\t\tilmClient := &client.ILMClient{\n\t\t\t\t\tClient: c,\n\t\t\t\t\tLogger: logger,\n\t\t\t\t}\n\t\t\t\treturn &initialize.Action{\n\t\t\t\t\tIndicesClient: indicesClient,\n\t\t\t\t\tClusterClient: clusterClient,\n\t\t\t\t\tILMClient:     ilmClient,\n\t\t\t\t\tConfig:        *initCfg,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t}\n\n\t// Rollover command\n\trolloverCfg := &rollover.Config{}\n\n\trolloverCommand := &cobra.Command{\n\t\tUse:   \"rollover http://HOSTNAME:PORT\",\n\t\tShort: \"rollover to new write index\",\n\t\tLong:  \"rollover to new write index\",\n\t\tArgs:  cobra.ExactArgs(1),\n\t\tRunE: func(_ *cobra.Command, args []string) error {\n\t\t\trolloverCfg.InitFromViper(v)\n\t\t\treturn app.ExecuteAction(app.ActionExecuteOptions{\n\t\t\t\tArgs:   args,\n\t\t\t\tViper:  v,\n\t\t\t\tLogger: logger,\n\t\t\t}, func(c client.Client, cfg app.Config) app.Action {\n\t\t\t\trolloverCfg.Config = cfg\n\t\t\t\trolloverCfg.InitFromViper(v)\n\t\t\t\tindicesClient := &client.IndicesClient{\n\t\t\t\t\tClient:               c,\n\t\t\t\t\tMasterTimeoutSeconds: rolloverCfg.Timeout,\n\t\t\t\t}\n\n\t\t\t\treturn &rollover.Action{\n\t\t\t\t\tIndicesClient: indicesClient,\n\t\t\t\t\tConfig:        *rolloverCfg,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t}\n\n\tlookbackCfg := lookback.Config{}\n\tlookbackCommand := &cobra.Command{\n\t\tUse:   \"lookback http://HOSTNAME:PORT\",\n\t\tShort: \"removes old indices from read alias\",\n\t\tLong:  \"removes old indices from read alias\",\n\t\tArgs:  cobra.ExactArgs(1),\n\t\tRunE: func(_ *cobra.Command, args []string) error {\n\t\t\tlookbackCfg.InitFromViper(v)\n\t\t\treturn app.ExecuteAction(app.ActionExecuteOptions{\n\t\t\t\tArgs:   args,\n\t\t\t\tViper:  v,\n\t\t\t\tLogger: logger,\n\t\t\t}, func(c client.Client, cfg app.Config) app.Action {\n\t\t\t\tlookbackCfg.Config = cfg\n\t\t\t\tlookbackCfg.InitFromViper(v)\n\t\t\t\tindicesClient := &client.IndicesClient{\n\t\t\t\t\tClient:               c,\n\t\t\t\t\tMasterTimeoutSeconds: lookbackCfg.Timeout,\n\t\t\t\t}\n\t\t\t\treturn &lookback.Action{\n\t\t\t\t\tIndicesClient: indicesClient,\n\t\t\t\t\tConfig:        lookbackCfg,\n\t\t\t\t\tLogger:        logger,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t}\n\n\taddPersistentFlags(v, rootCmd, app.AddFlags)\n\taddSubCommand(v, rootCmd, initCommand, initCfg.AddFlags)\n\taddSubCommand(v, rootCmd, rolloverCommand, rolloverCfg.AddFlags)\n\taddSubCommand(v, rootCmd, lookbackCommand, lookbackCfg.AddFlags)\n\n\tif err := rootCmd.Execute(); err != nil {\n\t\tos.Exit(1)\n\t}\n}\n\nfunc addSubCommand(v *viper.Viper, rootCmd, cmd *cobra.Command, addFlags func(*flag.FlagSet)) {\n\trootCmd.AddCommand(cmd)\n\tconfig.AddFlags(\n\t\tv,\n\t\tcmd,\n\t\taddFlags,\n\t)\n}\n\nfunc addPersistentFlags(v *viper.Viper, rootCmd *cobra.Command, inits ...func(*flag.FlagSet)) {\n\tflagSet := new(flag.FlagSet)\n\tfor i := range inits {\n\t\tinits[i](flagSet)\n\t}\n\trootCmd.PersistentFlags().AddGoFlagSet(flagSet)\n\tv.BindPFlags(rootCmd.PersistentFlags())\n}\n"
  },
  {
    "path": "cmd/esmapping-generator/main.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/mappings\"\n\t\"github.com/jaegertracing/jaeger/internal/version\"\n)\n\nfunc main() {\n\tesmappingsCmd := mappings.Command()\n\tesmappingsCmd.AddCommand(version.Command())\n\n\tif err := esmappingsCmd.Execute(); err != nil {\n\t\tfmt.Println(err.Error())\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/internal/docs/.gitignore",
    "content": "*.md\n*.rst\n*.1\n*.yaml\n"
  },
  {
    "path": "cmd/internal/docs/command.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage docs\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/cobra/doc\"\n\t\"github.com/spf13/viper\"\n)\n\nconst (\n\tformatFlag = \"format\"\n\tdirFlag    = \"dir\"\n)\n\nvar formats = []string{\"md\", \"man\", \"rst\", \"yaml\"}\n\n// Command for generating flags/commands documentation.\n// It generates the documentation for all commands starting at parent.\nfunc Command(v *viper.Viper) *cobra.Command {\n\tc := &cobra.Command{\n\t\tUse:   \"docs\",\n\t\tShort: \"Generates documentation\",\n\t\tLong:  `Generates command and flags documentation`,\n\t\tRunE: func(cmd *cobra.Command, _ /* args */ []string) error {\n\t\t\tfor cmd.Parent() != nil {\n\t\t\t\tcmd = cmd.Parent()\n\t\t\t}\n\t\t\tdir := v.GetString(dirFlag)\n\t\t\tlog.Printf(\"Generating documentation in %v\", dir)\n\t\t\tswitch v.GetString(formatFlag) {\n\t\t\tcase \"md\":\n\t\t\t\treturn doc.GenMarkdownTree(cmd, dir)\n\t\t\tcase \"man\":\n\t\t\t\treturn genMan(cmd, dir)\n\t\t\tcase \"rst\":\n\t\t\t\treturn doc.GenReSTTree(cmd, dir)\n\t\t\tcase \"yaml\":\n\t\t\t\treturn doc.GenYamlTree(cmd, dir)\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"undefined value of %v, possible values are: %v\", formatFlag, formats)\n\t\t\t}\n\t\t},\n\t}\n\tc.Flags().AddGoFlagSet(flags(&flag.FlagSet{}))\n\tv.BindPFlags(c.Flags())\n\treturn c\n}\n\nfunc flags(flagSet *flag.FlagSet) *flag.FlagSet {\n\tflagSet.String(\n\t\tformatFlag,\n\t\tformats[0],\n\t\tfmt.Sprintf(\"Supported formats: %v.\", formats))\n\tflagSet.String(\n\t\tdirFlag,\n\t\t\"./\",\n\t\t\"Directory where generate the documentation.\")\n\treturn flagSet\n}\n\nfunc genMan(cmd *cobra.Command, dir string) error {\n\theader := &doc.GenManHeader{\n\t\tTitle:   cmd.Use,\n\t\tSection: \"1\",\n\t}\n\treturn doc.GenManTree(cmd, header, dir)\n}\n"
  },
  {
    "path": "cmd/internal/docs/command_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage docs\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestOutputFormats(t *testing.T) {\n\ttests := []struct {\n\t\tfile string\n\t\tflag string\n\t\terr  string\n\t}{\n\t\t{file: \"docs.md\"},\n\t\t{file: \"docs.1\", flag: \"--format=man\"},\n\t\t{file: \"docs.rst\", flag: \"--format=rst\"},\n\t\t{file: \"docs.yaml\", flag: \"--format=yaml\"},\n\t\t{flag: \"--format=foo\", err: \"undefined value of format, possible values are: [md man rst yaml]\"},\n\t}\n\tfor _, test := range tests {\n\t\tv := viper.New()\n\t\tcmd := Command(v)\n\t\tcmd.ParseFlags([]string{test.flag})\n\t\terr := cmd.Execute()\n\t\tif err == nil {\n\t\t\tf, err := os.ReadFile(test.file)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Contains(t, string(f), \"documentation\")\n\t\t} else {\n\t\t\tassert.Equal(t, test.err, err.Error())\n\t\t}\n\t}\n}\n\nfunc TestDocsForParent(t *testing.T) {\n\tparent := &cobra.Command{\n\t\tUse:   \"root_command\",\n\t\tShort: \"some description\",\n\t}\n\tv := viper.New()\n\tdocs := Command(v)\n\tparent.AddCommand(docs)\n\terr := docs.RunE(docs, []string{})\n\trequire.NoError(t, err)\n\tf, err := os.ReadFile(\"root_command.md\")\n\trequire.NoError(t, err)\n\tassert.Contains(t, string(f), \"some description\")\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/internal/featuregate/command.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage featuregate\n\nimport (\n\t\"github.com/spf13/cobra\"\n\t\"go.opentelemetry.io/collector/otelcol\"\n)\n\nfunc Command() *cobra.Command {\n\treturn newCommand(func() *cobra.Command {\n\t\tsettings := otelcol.CollectorSettings{}\n\t\treturn otelcol.NewCommand(settings)\n\t})\n}\n\nfunc newCommand(otelCmdFn func() *cobra.Command) *cobra.Command {\n\totelCmd := otelCmdFn()\n\tfor _, cmd := range otelCmd.Commands() {\n\t\tif cmd.Name() == \"featuregate\" {\n\t\t\totelCmd.RemoveCommand(cmd)\n\t\t\treturn cmd\n\t\t}\n\t}\n\tpanic(\"could not find 'featuregate' command\")\n}\n"
  },
  {
    "path": "cmd/internal/featuregate/command_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage featuregate\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCommand(t *testing.T) {\n\tcmd := Command()\n\tassert.Equal(t, \"featuregate [feature-id]\", cmd.Use)\n}\n\nfunc TestCommand_Panic(t *testing.T) {\n\tassert.PanicsWithValue(t, \"could not find 'featuregate' command\", func() {\n\t\tnewCommand(func() *cobra.Command {\n\t\t\treturn &cobra.Command{}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "cmd/internal/featuregate/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage featuregate\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/internal/flags/admin.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/pprof\"\n\t\"sync\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/config/confighttp\"\n\t\"go.opentelemetry.io/collector/config/confignet\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config/tlscfg\"\n\t\"github.com/jaegertracing/jaeger/internal/recoveryhandler\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/version\"\n)\n\nconst (\n\tadminHTTPHostPort = \"admin.http.host-port\"\n)\n\nvar tlsAdminHTTPFlagsConfig = tlscfg.ServerFlagsConfig{\n\tPrefix: \"admin.http\",\n}\n\n// AdminServer runs an HTTP server with admin endpoints, such as /metrics, /debug/pprof, health check, etc.\ntype AdminServer struct {\n\tlogger    *zap.Logger\n\tmux       *http.ServeMux\n\tserver    *http.Server\n\tserverCfg confighttp.ServerConfig\n\tstopped   sync.WaitGroup\n\thc        *HealthHost\n}\n\n// NewAdminServer creates a new admin server.\nfunc NewAdminServer(hostPort string) *AdminServer {\n\treturn &AdminServer{\n\t\tlogger: zap.NewNop(),\n\t\tmux:    http.NewServeMux(),\n\t\tserverCfg: confighttp.ServerConfig{\n\t\t\tNetAddr: confignet.AddrConfig{\n\t\t\t\tEndpoint:  hostPort,\n\t\t\t\tTransport: confignet.TransportTypeTCP,\n\t\t\t},\n\t\t},\n\t\thc: NewHealthHost(),\n\t}\n}\n\n// Host returns the health host for this admin server.\n// It implements component.Host and componentstatus.Reporter,\n// allowing it to be used with telemetry.Settings and componentstatus.ReportStatus.\nfunc (s *AdminServer) Host() *HealthHost {\n\treturn s.hc\n}\n\n// setLogger initializes logger.\nfunc (s *AdminServer) setLogger(logger *zap.Logger) {\n\ts.logger = logger\n}\n\n// AddFlags registers CLI flags.\nfunc (s *AdminServer) AddFlags(flagSet *flag.FlagSet) {\n\tflagSet.String(adminHTTPHostPort, s.serverCfg.NetAddr.Endpoint, fmt.Sprintf(\"The host:port (e.g. 127.0.0.1%s or %s) for the admin server, including health check, /metrics, etc.\", s.serverCfg.NetAddr.Endpoint, s.serverCfg.NetAddr.Endpoint))\n\ttlsAdminHTTPFlagsConfig.AddFlags(flagSet)\n}\n\n// InitFromViper initializes the server with properties retrieved from Viper.\nfunc (s *AdminServer) initFromViper(v *viper.Viper, logger *zap.Logger) error {\n\ts.setLogger(logger)\n\n\ttlsAdminHTTP, err := tlsAdminHTTPFlagsConfig.InitFromViper(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse admin server TLS options: %w\", err)\n\t}\n\n\ts.serverCfg.NetAddr.Endpoint = v.GetString(adminHTTPHostPort)\n\ts.serverCfg.TLS = tlsAdminHTTP\n\treturn nil\n}\n\n// Handle adds a new handler to the admin server.\nfunc (s *AdminServer) Handle(path string, handler http.Handler) {\n\ts.mux.Handle(path, handler)\n}\n\n// Serve starts HTTP server.\nfunc (s *AdminServer) Serve() error {\n\tl, err := s.serverCfg.ToListener(context.Background())\n\tif err != nil {\n\t\ts.logger.Error(\"Admin server failed to listen\", zap.Error(err))\n\t\treturn err\n\t}\n\n\treturn s.serveWithListener(l)\n}\n\nfunc (s *AdminServer) serveWithListener(l net.Listener) (err error) {\n\ts.logger.Info(\"Mounting health check on admin server\", zap.String(\"route\", \"/\"))\n\ts.mux.Handle(\"/\", s.hc.Handler())\n\tversion.RegisterHandler(s.mux, s.logger)\n\ts.registerPprofHandlers()\n\trecoveryHandler := recoveryhandler.NewRecoveryHandler(s.logger, true)\n\ts.server, err = s.serverCfg.ToServer(\n\t\tcontext.Background(),\n\t\tnil, // host\n\t\ttelemetry.NoopSettings().ToOtelComponent(),\n\t\trecoveryHandler(s.mux),\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create admin server: %w\", err)\n\t}\n\terrorLog, _ := zap.NewStdLogAt(s.logger, zapcore.ErrorLevel)\n\ts.server.ErrorLog = errorLog\n\n\ts.logger.Info(\"Starting admin HTTP server\")\n\tvar wg sync.WaitGroup\n\t//nolint:revive // not the same as wg.Go() which would call Done() on exit, not on start\n\twg.Add(1)\n\ts.stopped.Add(1)\n\tgo func() {\n\t\twg.Done()\n\t\tdefer s.stopped.Done()\n\t\terr := s.server.Serve(l)\n\t\tif err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\ts.logger.Error(\"failed to serve\", zap.Error(err))\n\t\t\ts.hc.SetUnavailable()\n\t\t}\n\t}()\n\twg.Wait() // wait for the server to start listening\n\ts.logger.Info(\"Admin server started\", zap.String(\"http.host-port\", l.Addr().String()))\n\treturn nil\n}\n\nfunc (s *AdminServer) registerPprofHandlers() {\n\ts.mux.HandleFunc(\"/debug/pprof/\", pprof.Index)\n\ts.mux.HandleFunc(\"/debug/pprof/cmdline\", pprof.Cmdline)\n\ts.mux.HandleFunc(\"/debug/pprof/profile\", pprof.Profile)\n\ts.mux.HandleFunc(\"/debug/pprof/symbol\", pprof.Symbol)\n\ts.mux.HandleFunc(\"/debug/pprof/trace\", pprof.Trace)\n\ts.mux.Handle(\"/debug/pprof/goroutine\", pprof.Handler(\"goroutine\"))\n\ts.mux.Handle(\"/debug/pprof/heap\", pprof.Handler(\"heap\"))\n\ts.mux.Handle(\"/debug/pprof/threadcreate\", pprof.Handler(\"threadcreate\"))\n\ts.mux.Handle(\"/debug/pprof/block\", pprof.Handler(\"block\"))\n}\n\n// Close stops the HTTP server\nfunc (s *AdminServer) Close() error {\n\terr := s.server.Shutdown(context.Background())\n\ts.stopped.Wait()\n\treturn err\n}\n"
  },
  {
    "path": "cmd/internal/flags/admin_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\t\"go.uber.org/zap/zaptest/observer\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n\t\"github.com/jaegertracing/jaeger/ports\"\n)\n\nvar testCertKeyLocation = \"../../../internal/config/tlscfg/testdata\"\n\nfunc TestAdminServerHealthCheck(t *testing.T) {\n\tadminServer := NewAdminServer(\":0\")\n\n\tv, _ := config.Viperize(adminServer.AddFlags)\n\tzapCore, logs := observer.New(zap.InfoLevel)\n\tlogger := zap.New(zapCore)\n\trequire.NoError(t, adminServer.initFromViper(v, logger))\n\trequire.NoError(t, adminServer.Serve())\n\tdefer adminServer.Close()\n\n\t// Get the actual address from the log\n\tmessage := logs.FilterMessage(\"Admin server started\")\n\trequire.Equal(t, 1, message.Len())\n\thostPort := message.All()[0].ContextMap()[\"http.host-port\"].(string)\n\n\t// Health check should initially be unavailable (503)\n\tresp, err := http.Get(fmt.Sprintf(\"http://%s/\", hostPort))\n\trequire.NoError(t, err)\n\tresp.Body.Close()\n\tassert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)\n\n\t// Set to ready - should return 204\n\tadminServer.Host().Ready()\n\tresp, err = http.Get(fmt.Sprintf(\"http://%s/\", hostPort))\n\trequire.NoError(t, err)\n\tresp.Body.Close()\n\tassert.Equal(t, http.StatusNoContent, resp.StatusCode)\n\n\t// Set to unavailable - should return 503\n\tadminServer.Host().SetUnavailable()\n\tresp, err = http.Get(fmt.Sprintf(\"http://%s/\", hostPort))\n\trequire.NoError(t, err)\n\tresp.Body.Close()\n\tassert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)\n}\n\nfunc TestAdminServerHandlesPortZero(t *testing.T) {\n\tadminServer := NewAdminServer(\":0\")\n\n\tv, _ := config.Viperize(adminServer.AddFlags)\n\n\tzapCore, logs := observer.New(zap.InfoLevel)\n\tlogger := zap.New(zapCore)\n\n\tadminServer.initFromViper(v, logger)\n\n\trequire.NoError(t, adminServer.Serve())\n\tdefer adminServer.Close()\n\n\tmessage := logs.FilterMessage(\"Admin server started\")\n\tassert.Equal(t, 1, message.Len(), \"Expected Admin server started log message.\")\n\n\tonlyEntry := message.All()[0]\n\thostPort := onlyEntry.ContextMap()[\"http.host-port\"].(string)\n\tport, _ := strconv.Atoi(strings.Split(hostPort, \":\")[3])\n\tassert.Positive(t, port)\n}\n\nfunc TestAdminWithFailedFlags(t *testing.T) {\n\tadminServer := NewAdminServer(fmt.Sprintf(\":%d\", ports.RemoteStorageAdminHTTP))\n\tzapCore, _ := observer.New(zap.InfoLevel)\n\tlogger := zap.New(zapCore)\n\tv, command := config.Viperize(adminServer.AddFlags)\n\terr := command.ParseFlags([]string{\n\t\t\"--admin.http.tls.enabled=false\",\n\t\t\"--admin.http.tls.cert=blah\", // invalid unless tls.enabled\n\t})\n\trequire.NoError(t, err)\n\terr = adminServer.initFromViper(v, logger)\n\tassert.ErrorContains(t, err, \"failed to parse admin server TLS options\")\n}\n\nfunc TestAdminServerTLS(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tserverTLSFlags []string\n\t\tclientTLS      configtls.ClientConfig\n\t}{\n\t\t{\n\t\t\tname: \"should pass with TLS client to trusted TLS server with correct hostname\",\n\t\t\tserverTLSFlags: []string{\n\t\t\t\t\"--admin.http.tls.enabled=true\",\n\t\t\t\t\"--admin.http.tls.cert=\" + testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\t\"--admin.http.tls.key=\" + testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t\tclientTLS: configtls.ClientConfig{\n\t\t\t\tInsecure: false,\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCAFile: testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t\t\t},\n\t\t\t\tServerName: \"example.com\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range testCases {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tadminServer := NewAdminServer(fmt.Sprintf(\":%d\", ports.RemoteStorageAdminHTTP))\n\n\t\t\tv, command := config.Viperize(adminServer.AddFlags)\n\t\t\terr := command.ParseFlags(test.serverTLSFlags)\n\t\t\trequire.NoError(t, err)\n\n\t\t\terr = adminServer.initFromViper(v, zaptest.NewLogger(t))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tadminServer.Serve()\n\t\t\tdefer adminServer.Close()\n\n\t\t\tclientTLSCfg, err0 := test.clientTLS.LoadTLSConfig(context.Background())\n\t\t\trequire.NoError(t, err0)\n\t\t\tdialer := &net.Dialer{Timeout: 2 * time.Second}\n\t\t\tconn, clientError := tls.DialWithDialer(dialer, \"tcp\", fmt.Sprintf(\"localhost:%d\", ports.RemoteStorageAdminHTTP), clientTLSCfg)\n\t\t\trequire.NoError(t, clientError)\n\t\t\trequire.NoError(t, conn.Close())\n\n\t\t\tclient := &http.Client{\n\t\t\t\tTransport: &http.Transport{\n\t\t\t\t\tTLSClientConfig: clientTLSCfg,\n\t\t\t\t},\n\t\t\t}\n\t\t\turl := fmt.Sprintf(\"https://localhost:%d\", ports.RemoteStorageAdminHTTP)\n\t\t\treq, err := http.NewRequest(http.MethodGet, url, http.NoBody)\n\t\t\trequire.NoError(t, err)\n\t\t\treq.Close = true // avoid persistent connections which leak goroutines\n\t\t\tresponse, requestError := client.Do(req)\n\t\t\trequire.NoError(t, requestError)\n\t\t\tdefer response.Body.Close()\n\t\t\trequire.NotNil(t, response)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/internal/flags/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package flags defines command line flags that are shared by several jaeger components.\n// They are defined in this shared location so that if several components are wired into\n// a single binary (e.g. a local container of complete Jaeger backend) they can all share\n// the flags.\npackage flags\n"
  },
  {
    "path": "cmd/internal/flags/flags.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nconst (\n\tlogLevel    = \"log-level\"\n\tlogEncoding = \"log-encoding\" // json or console\n\tconfigFile  = \"config-file\"\n)\n\n// AddConfigFileFlag adds flags for ExternalConfFlags\nfunc AddConfigFileFlag(flagSet *flag.FlagSet) {\n\tflagSet.String(configFile, \"\", \"Configuration file in JSON, TOML, YAML, HCL, or Java properties formats (default none). See spf13/viper for precedence.\")\n}\n\n// TryLoadConfigFile initializes viper with config file specified as flag\nfunc TryLoadConfigFile(v *viper.Viper) error {\n\tif file := v.GetString(configFile); file != \"\" {\n\t\tv.SetConfigFile(file)\n\t\terr := v.ReadInConfig()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"cannot load config file %s: %w\", file, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// ParseJaegerTags parses the Jaeger tags string into a map.\nfunc ParseJaegerTags(jaegerTags string) (map[string]string, error) {\n\tif jaegerTags == \"\" {\n\t\treturn nil, nil\n\t}\n\ttagPairs := strings.Split(string(jaegerTags), \",\")\n\ttags := make(map[string]string)\n\tfor _, p := range tagPairs {\n\t\tkv := strings.SplitN(p, \"=\", 2)\n\t\tif len(kv) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid Jaeger tag pair %q, expected key=value\", p)\n\t\t}\n\t\tk, v := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])\n\n\t\tif strings.HasPrefix(v, \"${\") && strings.HasSuffix(v, \"}\") {\n\t\t\tskipWhenEmpty := false\n\n\t\t\ted := strings.SplitN(string(v[2:len(v)-1]), \":\", 2)\n\t\t\tif len(ed) == 1 {\n\t\t\t\t// no default value specified, set to empty\n\t\t\t\tskipWhenEmpty = true\n\t\t\t\ted = append(ed, \"\")\n\t\t\t}\n\n\t\t\te, d := ed[0], ed[1]\n\t\t\tv = os.Getenv(e)\n\t\t\tif v == \"\" && d != \"\" {\n\t\t\t\tv = d\n\t\t\t}\n\n\t\t\t// no value is set, skip this entry\n\t\t\tif v == \"\" && skipWhenEmpty {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\ttags[k] = v\n\t}\n\n\treturn tags, nil\n}\n\n// SharedFlags holds flags configuration\ntype SharedFlags struct {\n\t// Logging holds logging configuration\n\tLogging logging\n}\n\ntype logging struct {\n\tLevel    string\n\tEncoding string\n}\n\n// AddLoggingFlag adds logging flag for SharedFlags\nfunc AddLoggingFlags(flagSet *flag.FlagSet) {\n\tflagSet.String(logLevel, \"info\", \"Minimal allowed log Level. For more levels see https://github.com/uber-go/zap\")\n\tflagSet.String(logEncoding, \"json\", \"Log encoding. Supported values are 'json' and 'console'.\")\n}\n\n// InitFromViper initializes SharedFlags with properties from viper\nfunc (flags *SharedFlags) InitFromViper(v *viper.Viper) *SharedFlags {\n\tflags.Logging.Level = v.GetString(logLevel)\n\tflags.Logging.Encoding = v.GetString(logEncoding)\n\treturn flags\n}\n\n// NewLogger returns logger based on configuration in SharedFlags\nfunc (flags *SharedFlags) NewLogger(conf zap.Config, options ...zap.Option) (*zap.Logger, error) {\n\tvar level zapcore.Level\n\terr := (&level).UnmarshalText([]byte(flags.Logging.Level))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconf.Level = zap.NewAtomicLevelAt(level)\n\tconf.Encoding = flags.Logging.Encoding\n\tif flags.Logging.Encoding == \"console\" {\n\t\tconf.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder\n\t}\n\treturn conf.Build(options...)\n}\n"
  },
  {
    "path": "cmd/internal/flags/flags_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseJaegerTags(t *testing.T) {\n\ttags, err := ParseJaegerTags(\"\")\n\trequire.NoError(t, err)\n\tassert.Nil(t, tags)\n\n\tjaegerTags := fmt.Sprintf(\"%s,%s,%s,%s,%s,%s\",\n\t\t\"key=value\",\n\t\t\"envVar1=${envKey1:defaultVal1}\",\n\t\t\"envVar2=${envKey2:defaultVal2}\",\n\t\t\"envVar3=${envKey3}\",\n\t\t\"envVar4=${envKey4}\",\n\t\t\"envVar5=${envVar5:}\",\n\t)\n\n\tt.Setenv(\"envKey1\", \"envVal1\")\n\tt.Setenv(\"envKey4\", \"envVal4\")\n\n\texpectedTags := map[string]string{\n\t\t\"key\":     \"value\",\n\t\t\"envVar1\": \"envVal1\",\n\t\t\"envVar2\": \"defaultVal2\",\n\t\t\"envVar4\": \"envVal4\",\n\t\t\"envVar5\": \"\",\n\t}\n\n\ttags, err = ParseJaegerTags(jaegerTags)\n\trequire.NoError(t, err)\n\tassert.Equal(t, expectedTags, tags)\n}\n\nfunc TestParseJaegerTagsError(t *testing.T) {\n\t_, err := ParseJaegerTags(\"no-equals-sign\")\n\trequire.Error(t, err)\n\tassert.ErrorContains(t, err, \"invalid Jaeger tag pair\")\n}\n"
  },
  {
    "path": "cmd/internal/flags/healthhost.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"net/http\"\n\t\"sync/atomic\"\n\n\t\"go.opentelemetry.io/collector/component\"\n\t\"go.opentelemetry.io/collector/component/componentstatus\"\n)\n\nvar (\n\t_ component.Host           = (*HealthHost)(nil)\n\t_ componentstatus.Reporter = (*HealthHost)(nil)\n)\n\n// HealthHost implements component.Host and componentstatus.Reporter\n// and provides an HTTP handler for health checks.\ntype HealthHost struct {\n\tready atomic.Bool\n}\n\n// NewHealthHost creates a new HealthHost in not-ready state.\nfunc NewHealthHost() *HealthHost {\n\treturn &HealthHost{}\n}\n\n// GetExtensions implements component.Host.\nfunc (*HealthHost) GetExtensions() map[component.ID]component.Component {\n\treturn nil\n}\n\n// Report implements componentstatus.Reporter.\nfunc (h *HealthHost) Report(event *componentstatus.Event) {\n\tswitch event.Status() {\n\tcase componentstatus.StatusOK, componentstatus.StatusRecoverableError:\n\t\th.Ready()\n\tdefault:\n\t\th.SetUnavailable()\n\t}\n}\n\n// Ready sets the health status to ready.\nfunc (h *HealthHost) Ready() {\n\th.ready.Store(true)\n}\n\n// SetUnavailable sets the health status to unavailable.\nfunc (h *HealthHost) SetUnavailable() {\n\th.ready.Store(false)\n}\n\n// Handler returns an HTTP handler for the health endpoint.\n// Returns 204 No Content when ready, 503 Service Unavailable otherwise.\nfunc (h *HealthHost) Handler() http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tif h.ready.Load() {\n\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusServiceUnavailable)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "cmd/internal/flags/healthhost_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/component/componentstatus\"\n)\n\nfunc TestHealthHost_Handler(t *testing.T) {\n\thh := NewHealthHost()\n\thandler := hh.Handler()\n\n\t// Initially unavailable - should return 503\n\treq := httptest.NewRequest(http.MethodGet, \"/\", http.NoBody)\n\tw := httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusServiceUnavailable, w.Code)\n\n\t// Set to ready - should return 204\n\thh.Ready()\n\tw = httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\n\t// Set to unavailable - should return 503\n\thh.SetUnavailable()\n\tw = httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusServiceUnavailable, w.Code)\n}\n\nfunc TestHealthHost_Report(t *testing.T) {\n\thh := NewHealthHost()\n\thandler := hh.Handler()\n\treq := httptest.NewRequest(http.MethodGet, \"/\", http.NoBody)\n\n\t// StatusOK should set Ready\n\thh.Report(componentstatus.NewEvent(componentstatus.StatusOK))\n\tw := httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\n\t// StatusStopping should set Unavailable\n\thh.Report(componentstatus.NewEvent(componentstatus.StatusStopping))\n\tw = httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusServiceUnavailable, w.Code)\n\n\t// StatusRecoverableError should set Ready\n\thh.Report(componentstatus.NewRecoverableErrorEvent(nil))\n\tw = httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusNoContent, w.Code)\n\n\t// StatusFatalError should set Unavailable\n\thh.Report(componentstatus.NewFatalErrorEvent(nil))\n\tw = httptest.NewRecorder()\n\thandler.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusServiceUnavailable, w.Code)\n}\n\nfunc TestHealthHost_GetExtensions(t *testing.T) {\n\thh := NewHealthHost()\n\tassert.Nil(t, hh.GetExtensions())\n}\n"
  },
  {
    "path": "cmd/internal/flags/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/internal/flags/service.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"expvar\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/featuregate\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapgrpc\"\n\t\"google.golang.org/grpc/grpclog\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics/metricsbuilder\"\n\t\"github.com/jaegertracing/jaeger/ports\"\n)\n\n// Service represents an abstract Jaeger backend component with some basic shared functionality.\ntype Service struct {\n\t// AdminPort is the HTTP port number for admin server.\n\tAdminPort int\n\n\t// Admin is the admin server that hosts the health check and metrics endpoints.\n\tAdmin *AdminServer\n\n\t// Logger is initialized after parsing Viper flags like --log-level.\n\tLogger *zap.Logger\n\n\t// MetricsFactory is the root factory without a namespace.\n\tMetricsFactory metrics.Factory\n\n\tsignalsChannel chan os.Signal\n}\n\n// NewService creates a new Service.\nfunc NewService(adminPort int) *Service {\n\tsignalsChannel := make(chan os.Signal, 1)\n\tsignal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)\n\n\treturn &Service{\n\t\tAdmin:          NewAdminServer(ports.PortToHostPort(adminPort)),\n\t\tsignalsChannel: signalsChannel,\n\t}\n}\n\n// AddFlags registers CLI flags.\nfunc (s *Service) AddFlags(flagSet *flag.FlagSet) {\n\tAddConfigFileFlag(flagSet)\n\tAddLoggingFlags(flagSet)\n\tmetricsbuilder.AddFlags(flagSet)\n\ts.Admin.AddFlags(flagSet)\n\tfeaturegate.GlobalRegistry().RegisterFlags(flagSet)\n}\n\n// Start bootstraps the service and starts the admin server.\nfunc (s *Service) Start(v *viper.Viper) error {\n\tif err := TryLoadConfigFile(v); err != nil {\n\t\treturn fmt.Errorf(\"cannot load config file: %w\", err)\n\t}\n\n\tsFlags := new(SharedFlags).InitFromViper(v)\n\tnewProdConfig := zap.NewProductionConfig()\n\tnewProdConfig.Sampling = nil\n\tlogger, err := sFlags.NewLogger(newProdConfig)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot create logger: %w\", err)\n\t}\n\ts.Logger = logger\n\tgrpclog.SetLoggerV2(zapgrpc.NewLogger(\n\t\tlogger.WithOptions(\n\t\t\tzap.AddCallerSkip(5), // ensure the actual caller:lineNo is shown\n\t\t)))\n\n\tmetricsBuilder := new(metricsbuilder.Builder).InitFromViper(v)\n\tmetricsFactory, err := metricsBuilder.CreateMetricsFactory(\"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot create metrics factory: %w\", err)\n\t}\n\ts.MetricsFactory = metricsFactory\n\n\tif err = s.Admin.initFromViper(v, s.Logger); err != nil {\n\t\treturn fmt.Errorf(\"cannot initialize admin server: %w\", err)\n\t}\n\tif h := metricsBuilder.Handler(); h != nil {\n\t\troute := metricsBuilder.HTTPRoute\n\t\ts.Logger.Info(\"Mounting metrics handler on admin server\", zap.String(\"route\", route))\n\t\ts.Admin.Handle(route, h)\n\t}\n\n\t// Mount expvar routes on different backends\n\tif metricsBuilder.Backend != \"expvar\" {\n\t\ts.Logger.Info(\"Mounting expvar handler on admin server\", zap.String(\"route\", \"/debug/vars\"))\n\t\ts.Admin.Handle(\"/debug/vars\", expvar.Handler())\n\t}\n\n\tif err := s.Admin.Serve(); err != nil {\n\t\treturn fmt.Errorf(\"cannot start the admin server: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// RunAndThen sets the health check to Ready and blocks until SIGTERM is received.\n// It then runs the shutdown function and exits.\nfunc (s *Service) RunAndThen(shutdown func()) error {\n\ts.Admin.Host().Ready()\n\n\t<-s.signalsChannel\n\n\ts.Logger.Info(\"Shutting down\")\n\ts.Admin.Host().SetUnavailable()\n\n\tif shutdown != nil {\n\t\tshutdown()\n\t}\n\n\terr := s.Admin.Close()\n\tif err == nil {\n\t\ts.Logger.Info(\"Shutdown complete\")\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "cmd/internal/flags/service_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage flags\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"reflect\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n)\n\nfunc TestAddFlags(*testing.T) {\n\ts := NewService(0)\n\ts.AddFlags(new(flag.FlagSet))\n}\n\nfunc TestStartErrors(t *testing.T) {\n\tscenarios := []struct {\n\t\tname   string\n\t\tflags  []string\n\t\texpErr string\n\t}{\n\t\t{\n\t\t\tname:   \"bad config\",\n\t\t\tflags:  []string{\"--config-file=invalid-file-name\"},\n\t\t\texpErr: \"cannot load config file\",\n\t\t},\n\t\t{\n\t\t\tname:   \"bad log level\",\n\t\t\tflags:  []string{\"--log-level=invalid-log-level\"},\n\t\t\texpErr: \"cannot create logger\",\n\t\t},\n\t\t{\n\t\t\tname:   \"bad metrics backend\",\n\t\t\tflags:  []string{\"--metrics-backend=invalid-metrics-backend\"},\n\t\t\texpErr: \"cannot create metrics factory\",\n\t\t},\n\t\t{\n\t\t\tname:   \"bad admin TLS\",\n\t\t\tflags:  []string{\"--admin.http.tls.enabled=true\", \"--admin.http.tls.cert=invalid-cert\"},\n\t\t\texpErr: \"cannot start the admin server: failed to load TLS config\",\n\t\t},\n\t\t{\n\t\t\tname:   \"bad host:port\",\n\t\t\tflags:  []string{\"--admin.http.host-port=invalid\"},\n\t\t\texpErr: \"cannot start the admin server\",\n\t\t},\n\t\t{\n\t\t\tname:  \"clean start\",\n\t\t\tflags: []string{},\n\t\t},\n\t}\n\tfor _, test := range scenarios {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := NewService( /* default port= */ 0)\n\t\t\tv, cmd := config.Viperize(s.AddFlags)\n\t\t\terr := cmd.ParseFlags(test.flags)\n\t\t\trequire.NoError(t, err)\n\t\t\terr = s.Start(v)\n\t\t\tif test.expErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar stopped atomic.Bool\n\t\t\tshutdown := func() {\n\t\t\t\tstopped.Store(true)\n\t\t\t}\n\t\t\tgo s.RunAndThen(shutdown)\n\n\t\t\t// Give time for RunAndThen to start\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\t\ts.signalsChannel <- os.Interrupt\n\t\t\twaitForEqual(t, true, func() any { return stopped.Load() })\n\t\t})\n\t}\n}\n\nfunc waitForEqual(t *testing.T, expected any, getter func() any) {\n\tfor range 1000 {\n\t\tvalue := getter()\n\t\tif reflect.DeepEqual(value, expected) {\n\t\t\treturn\n\t\t}\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n\tassert.Equal(t, expected, getter())\n}\n"
  },
  {
    "path": "cmd/internal/printconfig/command.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage printconfig\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nfunc printDivider(cmd *cobra.Command, n int) {\n\tfmt.Fprint(cmd.OutOrStdout(), strings.Repeat(\"-\", n), \"\\n\")\n}\n\nfunc printConfigurations(cmd *cobra.Command, v *viper.Viper, includeEmpty bool) {\n\tkeys := v.AllKeys()\n\tsort.Strings(keys)\n\n\tmaxKeyLength, maxValueLength := len(\"Configuration Option Name\"), len(\"Value\")\n\tmaxSourceLength := len(\"user-assigned\")\n\tfor _, key := range keys {\n\t\tvalue := v.GetString(key)\n\t\tif len(key) > maxKeyLength {\n\t\t\tmaxKeyLength = len(key)\n\t\t}\n\t\tif len(value) > maxValueLength {\n\t\t\tmaxValueLength = len(value)\n\t\t}\n\t}\n\tmaxRowLength := maxKeyLength + maxValueLength + maxSourceLength + 6\n\n\tprintDivider(cmd, maxRowLength)\n\tfmt.Fprintf(cmd.OutOrStdout(),\n\t\t\"| %-*s %-*s %-*s |\\n\",\n\t\tmaxKeyLength, \"Configuration Option Name\",\n\t\tmaxValueLength, \"Value\",\n\t\tmaxSourceLength, \"Source\")\n\tprintDivider(cmd, maxRowLength)\n\n\tfor _, key := range keys {\n\t\tvalue := v.GetString(key)\n\t\tsource := \"default\"\n\t\tif v.IsSet(key) {\n\t\t\tsource = \"user-assigned\"\n\t\t}\n\n\t\tif includeEmpty || value != \"\" {\n\t\t\tfmt.Fprintf(cmd.OutOrStdout(),\n\t\t\t\t\"| %-*s %-*s %-*s |\\n\",\n\t\t\t\tmaxKeyLength, key,\n\t\t\t\tmaxValueLength, value,\n\t\t\t\tmaxSourceLength, source)\n\t\t}\n\t}\n\tprintDivider(cmd, maxRowLength)\n}\n\nfunc Command(v *viper.Viper) *cobra.Command {\n\tallFlag := true\n\tcmd := &cobra.Command{\n\t\tUse:   \"print-config\",\n\t\tShort: \"Print names and values of configuration options\",\n\t\tLong:  \"Print names and values of configuration options, distinguishing between default and user-assigned values\",\n\t\tRunE: func(cmd *cobra.Command, _ /* args */ []string) error {\n\t\t\tprintConfigurations(cmd, v, allFlag)\n\t\t\treturn nil\n\t\t},\n\t}\n\tcmd.Flags().BoolVarP(&allFlag, \"all\", \"a\", false, \"Print all configuration options including those with empty values\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/internal/printconfig/command_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage printconfig\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n\t\"github.com/jaegertracing/jaeger/internal/config/tlscfg\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst (\n\ttestPluginBinary             = \"test-plugin.binary\"\n\ttestPluginConfigurationFile  = \"test-plugin.configuration-file\"\n\ttestPluginLogLevel           = \"test-plugin.log-level\"\n\ttestRemotePrefix             = \"test-remote\"\n\ttestRemoteServer             = testRemotePrefix + \".server\"\n\ttestRemoteConnectionTimeout  = testRemotePrefix + \".connection-timeout\"\n\tdefaultTestPluginLogLevel    = \"warn\"\n\tdefaultTestConnectionTimeout = time.Duration(5 * time.Second)\n)\n\nfunc addFlags(flagSet *flag.FlagSet) {\n\ttlscfg.ClientFlagsConfig{\n\t\tPrefix: \"test\",\n\t}.AddFlags(flagSet)\n\n\tflagSet.String(testPluginBinary, \"\", \"\")\n\tflagSet.String(testPluginConfigurationFile, \"\", \"\")\n\tflagSet.String(testPluginLogLevel, defaultTestPluginLogLevel, \"\")\n\tflagSet.String(testRemoteServer, \"\", \"\")\n\tflagSet.Duration(testRemoteConnectionTimeout, defaultTestConnectionTimeout, \"\")\n}\n\nfunc setConfig(t *testing.T) *viper.Viper {\n\tv, command := config.Viperize(addFlags, tenancy.AddFlags)\n\terr := command.ParseFlags([]string{\n\t\t\"--test-plugin.binary=noop-test-plugin\",\n\t\t\"--test-plugin.configuration-file=config.json\",\n\t\t\"--test-plugin.log-level=debug\",\n\t\t\"--multi-tenancy.header=x-scope-orgid\",\n\t})\n\n\trequire.NoError(t, err)\n\n\treturn v\n}\n\nfunc runPrintConfigCommand(v *viper.Viper, t *testing.T, allFlag bool) string {\n\tbuf := new(bytes.Buffer)\n\tprintCmd := Command(v)\n\tprintCmd.SetOut(buf)\n\n\tif allFlag {\n\t\terr := printCmd.Flags().Set(\"all\", \"true\")\n\t\trequire.NoError(t, err, \"printCmd.Flags() returned the error %v\", err)\n\t}\n\n\t_, err := printCmd.ExecuteC()\n\trequire.NoError(t, err, \"printCmd.ExecuteC() returned the error %v\", err)\n\n\treturn buf.String()\n}\n\nfunc TestAllFlag(t *testing.T) {\n\texpected := `-----------------------------------------------------------------\n| Configuration Option Name      Value            Source        |\n-----------------------------------------------------------------\n| multi-tenancy.enabled          false            default       |\n| multi-tenancy.header           x-scope-orgid    user-assigned |\n| multi-tenancy.tenants                           default       |\n| test-plugin.binary             noop-test-plugin user-assigned |\n| test-plugin.configuration-file config.json      user-assigned |\n| test-plugin.log-level          debug            user-assigned |\n| test-remote.connection-timeout 5s               default       |\n| test-remote.server                              default       |\n| test.tls.ca                                     default       |\n| test.tls.cert                                   default       |\n| test.tls.enabled               false            default       |\n| test.tls.key                                    default       |\n| test.tls.server-name                            default       |\n| test.tls.skip-host-verify      false            default       |\n-----------------------------------------------------------------\n`\n\n\tv := setConfig(t)\n\tactual := runPrintConfigCommand(v, t, true)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestPrintConfigCommand(t *testing.T) {\n\texpected := `-----------------------------------------------------------------\n| Configuration Option Name      Value            Source        |\n-----------------------------------------------------------------\n| multi-tenancy.enabled          false            default       |\n| multi-tenancy.header           x-scope-orgid    user-assigned |\n| test-plugin.binary             noop-test-plugin user-assigned |\n| test-plugin.configuration-file config.json      user-assigned |\n| test-plugin.log-level          debug            user-assigned |\n| test-remote.connection-timeout 5s               default       |\n| test.tls.enabled               false            default       |\n| test.tls.skip-host-verify      false            default       |\n-----------------------------------------------------------------\n`\n\tv := setConfig(t)\n\tactual := runPrintConfigCommand(v, t, false)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/internal/status/command.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage status\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/jaegertracing/jaeger/ports\"\n)\n\nconst statusHTTPHostPort = \"status.http.host-port\"\n\n// Command for check component status.\nfunc Command(v *viper.Viper, adminPort int) *cobra.Command {\n\tc := &cobra.Command{\n\t\tUse:   \"status\",\n\t\tShort: \"Print the status.\",\n\t\tLong:  `Print Jaeger component status information, exit non-zero on any error.`,\n\t\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\t\turl := convert(v.GetString(statusHTTPHostPort))\n\t\t\tctx, cx := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cx()\n\t\t\treq, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\t\t\tresp, err := http.DefaultClient.Do(req) //nolint:gosec // G704 - URL from internal config\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\t\t\tbody, _ := io.ReadAll(resp.Body)\n\t\t\tfmt.Println(string(body))\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\treturn fmt.Errorf(\"abnormal value of http status code: %v\", resp.StatusCode)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tc.Flags().AddGoFlagSet(flags(&flag.FlagSet{}, adminPort))\n\tv.BindPFlags(c.Flags())\n\treturn c\n}\n\nfunc flags(flagSet *flag.FlagSet, adminPort int) *flag.FlagSet {\n\tadminPortStr := ports.PortToHostPort(adminPort)\n\tflagSet.String(statusHTTPHostPort, adminPortStr, fmt.Sprintf(\n\t\t\"The host:port (e.g. 127.0.0.1%s or %s) for the health check\", adminPortStr, adminPortStr))\n\treturn flagSet\n}\n\nfunc convert(httpHostPort string) string {\n\tif strings.HasPrefix(httpHostPort, \":\") {\n\t\treturn \"http://127.0.0.1\" + httpHostPort\n\t}\n\treturn \"http://\" + httpHostPort\n}\n"
  },
  {
    "path": "cmd/internal/status/command_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage status\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc readyHandler(w http.ResponseWriter, _ *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n\tw.Write([]byte(\"{\\\"status\\\":\\\"Server available\\\"}\"))\n}\n\nfunc unavailableHandler(w http.ResponseWriter, _ *http.Request) {\n\tw.WriteHeader(http.StatusServiceUnavailable)\n\tw.Write([]byte(\"{\\\"status\\\":\\\"Server not available\\\"}\"))\n}\n\nfunc TestReady(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(readyHandler))\n\tdefer ts.Close()\n\tv := viper.New()\n\tcmd := Command(v, 80)\n\tcmd.ParseFlags([]string{\"--status.http.host-port=\" + strings.TrimPrefix(ts.URL, \"http://\")})\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n}\n\nfunc TestOnlyPortConfig(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(readyHandler))\n\tdefer ts.Close()\n\tv := viper.New()\n\tcmd := Command(v, 80)\n\tcmd.ParseFlags([]string{\"--status.http.host-port=:\" + strings.Split(ts.URL, \":\")[len(strings.Split(ts.URL, \":\"))-1]})\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n}\n\nfunc TestUnready(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(unavailableHandler))\n\tdefer ts.Close()\n\tv := viper.New()\n\tcmd := Command(v, 80)\n\tcmd.ParseFlags([]string{\"--status.http.host-port=\" + strings.TrimPrefix(ts.URL, \"http://\")})\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestNoService(t *testing.T) {\n\tv := viper.New()\n\tcmd := Command(v, 12345)\n\terr := cmd.Execute()\n\trequire.Error(t, err)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/internal/storageconfig/config.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storageconfig\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/confmap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n\tcascfg \"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/metricstore/prometheus\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/grpc\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n)\n\nvar (\n\t_ confmap.Unmarshaler = (*TraceBackend)(nil)\n\t_ confmap.Unmarshaler = (*MetricBackend)(nil)\n)\n\n// Config contains configuration(s) for Jaeger trace storage.\ntype Config struct {\n\tTraceBackends  map[string]TraceBackend  `mapstructure:\"backends\"`\n\tMetricBackends map[string]MetricBackend `mapstructure:\"metric_backends\"`\n}\n\n// TraceBackend contains configuration for a single trace storage backend.\ntype TraceBackend struct {\n\tMemory        *memory.Configuration     `mapstructure:\"memory\"`\n\tBadger        *badger.Config            `mapstructure:\"badger\"`\n\tGRPC          *grpc.Config              `mapstructure:\"grpc\"`\n\tCassandra     *cassandra.Options        `mapstructure:\"cassandra\"`\n\tElasticsearch *escfg.Configuration      `mapstructure:\"elasticsearch\"`\n\tOpensearch    *escfg.Configuration      `mapstructure:\"opensearch\"`\n\tClickHouse    *clickhouse.Configuration `mapstructure:\"clickhouse\"`\n}\n\n// MetricBackend contains configuration for a single metric storage backend.\ntype MetricBackend struct {\n\tPrometheus    *PrometheusConfiguration `mapstructure:\"prometheus\"`\n\tElasticsearch *escfg.Configuration     `mapstructure:\"elasticsearch\"`\n\tOpensearch    *escfg.Configuration     `mapstructure:\"opensearch\"`\n}\n\ntype PrometheusConfiguration struct {\n\tConfiguration  promcfg.Configuration `mapstructure:\",squash\"`\n\tAuthentication escfg.Authentication  `mapstructure:\"auth\"`\n}\n\n// Unmarshal implements confmap.Unmarshaler. This allows us to provide\n// defaults for different configs.\nfunc (cfg *TraceBackend) Unmarshal(conf *confmap.Conf) error {\n\t// apply defaults\n\tif conf.IsSet(\"memory\") {\n\t\tcfg.Memory = &memory.Configuration{\n\t\t\tMaxTraces: 1_000_000,\n\t\t}\n\t}\n\tif conf.IsSet(\"badger\") {\n\t\tv := badger.DefaultConfig()\n\t\tcfg.Badger = v\n\t}\n\tif conf.IsSet(\"grpc\") {\n\t\tv := grpc.DefaultConfig()\n\t\tcfg.GRPC = &v\n\t}\n\tif conf.IsSet(\"cassandra\") {\n\t\tcfg.Cassandra = &cassandra.Options{\n\t\t\tConfiguration:          cascfg.DefaultConfiguration(),\n\t\t\tSpanStoreWriteCacheTTL: 12 * time.Hour,\n\t\t\tIndex: cassandra.IndexConfig{\n\t\t\t\tTags:        true,\n\t\t\t\tProcessTags: true,\n\t\t\t\tLogs:        true,\n\t\t\t},\n\t\t\tArchiveEnabled: false,\n\t\t}\n\t}\n\tif conf.IsSet(\"elasticsearch\") {\n\t\tv := es.DefaultConfig()\n\t\tcfg.Elasticsearch = &v\n\t}\n\tif conf.IsSet(\"opensearch\") {\n\t\tv := es.DefaultConfig()\n\t\tcfg.Opensearch = &v\n\t}\n\tif conf.IsSet(\"clickhouse\") {\n\t\tcfg.ClickHouse = &clickhouse.Configuration{}\n\t}\n\treturn conf.Unmarshal(cfg)\n}\n\nfunc (cfg *TraceBackend) Validate() error {\n\tvar backends []string\n\tif cfg.Memory != nil {\n\t\tbackends = append(backends, \"memory\")\n\t}\n\tif cfg.Badger != nil {\n\t\tbackends = append(backends, \"badger\")\n\t}\n\tif cfg.GRPC != nil {\n\t\tbackends = append(backends, \"grpc\")\n\t}\n\tif cfg.Cassandra != nil {\n\t\tbackends = append(backends, \"cassandra\")\n\t}\n\tif cfg.Elasticsearch != nil {\n\t\tbackends = append(backends, \"elasticsearch\")\n\t}\n\tif cfg.Opensearch != nil {\n\t\tbackends = append(backends, \"opensearch\")\n\t}\n\tif cfg.ClickHouse != nil {\n\t\tbackends = append(backends, \"clickhouse\")\n\t}\n\tif len(backends) == 0 {\n\t\treturn errors.New(\"empty configuration\")\n\t}\n\tif len(backends) > 1 {\n\t\treturn fmt.Errorf(\"multiple backend types found for trace storage: %v\", backends)\n\t}\n\treturn nil\n}\n\n// Unmarshal implements confmap.Unmarshaler for MetricBackend.\nfunc (cfg *MetricBackend) Unmarshal(conf *confmap.Conf) error {\n\t// apply defaults\n\tif conf.IsSet(\"prometheus\") {\n\t\tv := prometheus.DefaultConfig()\n\t\tcfg.Prometheus = &PrometheusConfiguration{\n\t\t\tConfiguration: v,\n\t\t}\n\t}\n\tif conf.IsSet(\"elasticsearch\") {\n\t\tv := es.DefaultConfig()\n\t\tcfg.Elasticsearch = &v\n\t}\n\tif conf.IsSet(\"opensearch\") {\n\t\tv := es.DefaultConfig()\n\t\tcfg.Opensearch = &v\n\t}\n\treturn conf.Unmarshal(cfg)\n}\n\nfunc (cfg *MetricBackend) Validate() error {\n\tvar backends []string\n\tif cfg.Prometheus != nil {\n\t\tbackends = append(backends, \"prometheus\")\n\t}\n\tif cfg.Elasticsearch != nil {\n\t\tbackends = append(backends, \"elasticsearch\")\n\t}\n\tif cfg.Opensearch != nil {\n\t\tbackends = append(backends, \"opensearch\")\n\t}\n\tif len(backends) == 0 {\n\t\treturn errors.New(\"empty configuration\")\n\t}\n\tif len(backends) > 1 {\n\t\treturn fmt.Errorf(\"multiple backend types found for metric storage: %v\", backends)\n\t}\n\treturn nil\n}\n\n// Validate validates the storage configuration.\nfunc (c *Config) Validate() error {\n\tif len(c.TraceBackends) == 0 {\n\t\treturn errors.New(\"at least one storage backend is required\")\n\t}\n\tfor name, b := range c.TraceBackends {\n\t\tif err := b.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"trace storage '%s': %w\", name, err)\n\t\t}\n\t}\n\tfor name, b := range c.MetricBackends {\n\t\tif err := b.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"metric storage '%s': %w\", name, err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/internal/storageconfig/config_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storageconfig\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/confmap\"\n\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n)\n\nfunc TestConfigValidate(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tconfig      Config\n\t\texpectError bool\n\t\terrorMsg    string\n\t}{\n\t\t{\n\t\t\tname: \"valid config with one backend\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{\n\t\t\t\t\t\"memory\": {\n\t\t\t\t\t\tMemory: &memory.Configuration{MaxTraces: 10000},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid config with multiple backends\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{\n\t\t\t\t\t\"memory1\": {\n\t\t\t\t\t\tMemory: &memory.Configuration{MaxTraces: 10000},\n\t\t\t\t\t},\n\t\t\t\t\t\"memory2\": {\n\t\t\t\t\t\tMemory: &memory.Configuration{MaxTraces: 20000},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"no backends\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"at least one storage backend is required\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty backend configuration\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{\n\t\t\t\t\t\"empty\": {},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"trace storage 'empty': empty configuration\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid metric backend\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{\n\t\t\t\t\t\"memory\": {Memory: &memory.Configuration{}},\n\t\t\t\t},\n\t\t\t\tMetricBackends: map[string]MetricBackend{\n\t\t\t\t\t\"prometheus\": {Prometheus: &PrometheusConfiguration{}},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid trace backend\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{\n\t\t\t\t\t\"invalid\": {\n\t\t\t\t\t\tMemory: &memory.Configuration{},\n\t\t\t\t\t\tBadger: &badger.Config{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"trace storage 'invalid': multiple backend types found\",\n\t\t},\n\t\t{\n\t\t\tname: \"invalid metric backend\",\n\t\t\tconfig: Config{\n\t\t\t\tTraceBackends: map[string]TraceBackend{\n\t\t\t\t\t\"memory\": {Memory: &memory.Configuration{}},\n\t\t\t\t},\n\t\t\t\tMetricBackends: map[string]MetricBackend{\n\t\t\t\t\t\"invalid\": {\n\t\t\t\t\t\tPrometheus:    &PrometheusConfiguration{},\n\t\t\t\t\t\tElasticsearch: &escfg.Configuration{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"metric storage 'invalid': multiple backend types found\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.config.Validate()\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tt.errorMsg)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraceBackendUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfigMap    map[string]any\n\t\texpectError  bool\n\t\tvalidateFunc func(*testing.T, *TraceBackend)\n\t}{\n\t\t{\n\t\t\tname: \"memory backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"memory\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.Memory)\n\t\t\t\tassert.Equal(t, 1_000_000, tb.Memory.MaxTraces)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"memory backend with custom value\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"memory\": map[string]any{\n\t\t\t\t\t\"max_traces\": 50000,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.Memory)\n\t\t\t\tassert.Equal(t, 50000, tb.Memory.MaxTraces)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"badger backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"badger\": map[string]any{\n\t\t\t\t\t\"ephemeral\": true,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.Badger)\n\t\t\t\tassert.True(t, tb.Badger.Ephemeral)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"grpc backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"grpc\": map[string]any{\n\t\t\t\t\t\"endpoint\": \"localhost:17271\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.GRPC)\n\t\t\t\tassert.Equal(t, \"localhost:17271\", tb.GRPC.ClientConfig.Endpoint)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"cassandra backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"cassandra\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.Cassandra)\n\t\t\t\tassert.True(t, tb.Cassandra.Index.Tags)\n\t\t\t\tassert.True(t, tb.Cassandra.Index.ProcessTags)\n\t\t\t\tassert.True(t, tb.Cassandra.Index.Logs)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"elasticsearch backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"elasticsearch\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.Elasticsearch)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"opensearch backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"opensearch\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, tb *TraceBackend) {\n\t\t\t\trequire.NotNil(t, tb.Opensearch)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconf := confmap.NewFromStringMap(tt.configMap)\n\t\t\tvar tb TraceBackend\n\t\t\terr := tb.Unmarshal(conf)\n\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tif tt.validateFunc != nil {\n\t\t\t\t\ttt.validateFunc(t, &tb)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMetricBackendUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfigMap    map[string]any\n\t\texpectError  bool\n\t\tvalidateFunc func(*testing.T, *MetricBackend)\n\t}{\n\t\t{\n\t\t\tname: \"prometheus backend with defaults\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"prometheus\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, mb *MetricBackend) {\n\t\t\t\trequire.NotNil(t, mb.Prometheus)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"elasticsearch backend\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"elasticsearch\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, mb *MetricBackend) {\n\t\t\t\trequire.NotNil(t, mb.Elasticsearch)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"opensearch backend\",\n\t\t\tconfigMap: map[string]any{\n\t\t\t\t\"opensearch\": map[string]any{},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\tvalidateFunc: func(t *testing.T, mb *MetricBackend) {\n\t\t\t\trequire.NotNil(t, mb.Opensearch)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconf := confmap.NewFromStringMap(tt.configMap)\n\t\t\tvar mb MetricBackend\n\t\t\terr := mb.Unmarshal(conf)\n\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tif tt.validateFunc != nil {\n\t\t\t\t\ttt.validateFunc(t, &mb)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc getStorageKeys(t reflect.Type) []string {\n\tvar keys []string\n\tfor field := range t.Fields() {\n\t\ttag := field.Tag.Get(\"mapstructure\")\n\t\tif tag != \"\" && tag != \",squash\" {\n\t\t\tkeys = append(keys, tag)\n\t\t}\n\t}\n\treturn keys\n}\n\nfunc TestTraceBackendExclusive(t *testing.T) {\n\tkeys := getStorageKeys(reflect.TypeFor[TraceBackend]())\n\tfor i := range keys {\n\t\tfor j := i + 1; j < len(keys); j++ {\n\t\t\tkey1 := keys[i]\n\t\t\tkey2 := keys[j]\n\t\t\tt.Run(fmt.Sprintf(\"%s+%s\", key1, key2), func(t *testing.T) {\n\t\t\t\tconf := confmap.NewFromStringMap(map[string]any{\n\t\t\t\t\tkey1: map[string]any{},\n\t\t\t\t\tkey2: map[string]any{},\n\t\t\t\t})\n\t\t\t\tvar tb TraceBackend\n\t\t\t\terr := tb.Unmarshal(conf)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = tb.Validate()\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), \"multiple backend types found\")\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestMetricBackendExclusive(t *testing.T) {\n\tkeys := getStorageKeys(reflect.TypeFor[MetricBackend]())\n\tfor i := range keys {\n\t\tfor j := i + 1; j < len(keys); j++ {\n\t\t\tkey1 := keys[i]\n\t\t\tkey2 := keys[j]\n\t\t\tt.Run(fmt.Sprintf(\"%s+%s\", key1, key2), func(t *testing.T) {\n\t\t\t\tconf := confmap.NewFromStringMap(map[string]any{\n\t\t\t\t\tkey1: map[string]any{},\n\t\t\t\t\tkey2: map[string]any{},\n\t\t\t\t})\n\t\t\t\tvar mb MetricBackend\n\t\t\t\terr := mb.Unmarshal(conf)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = mb.Validate()\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), \"multiple backend types found\")\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/internal/storageconfig/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storageconfig\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/grpc\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\n// AuthResolver is a function type that resolves an authenticator by name.\n// This allows the jaegerstorage extension to provide its authenticator resolution logic.\ntype AuthResolver func(authCfg escfg.Authentication, backendType, backendName string) (extensionauth.HTTPClient, error)\n\n// CreateTraceStorageFactory creates a trace storage factory from the backend configuration.\n// This is extracted from jaegerstorage extension to be shared between jaeger and remote-storage.\n// authResolver is optional; if nil, no authentication will be configured for ES/OS backends.\nfunc CreateTraceStorageFactory(\n\tctx context.Context,\n\tname string,\n\tbackend TraceBackend,\n\ttelset telemetry.Settings,\n\tauthResolver AuthResolver,\n) (tracestore.Factory, error) {\n\ttelset.Logger.Sugar().Infof(\"Initializing storage '%s'\", name)\n\n\t// Create scoped metrics factory\n\ttelset.Metrics = telset.Metrics.Namespace(metrics.NSOptions{\n\t\tName: \"storage\",\n\t\tTags: map[string]string{\n\t\t\t\"name\": name,\n\t\t\t\"role\": \"tracestore\",\n\t\t},\n\t})\n\n\tvar factory tracestore.Factory\n\tvar err error\n\n\tswitch {\n\tcase backend.Memory != nil:\n\t\tfactory, err = memory.NewFactory(*backend.Memory, telset)\n\tcase backend.Badger != nil:\n\t\tfactory, err = badger.NewFactory(*backend.Badger, telset)\n\tcase backend.GRPC != nil:\n\t\tfactory, err = grpc.NewFactory(ctx, *backend.GRPC, telset)\n\tcase backend.Cassandra != nil:\n\t\tfactory, err = cassandra.NewFactory(*backend.Cassandra, telset)\n\tcase backend.Elasticsearch != nil:\n\t\tvar httpAuth extensionauth.HTTPClient\n\t\tif authResolver != nil {\n\t\t\thttpAuth, err = authResolver(backend.Elasticsearch.Authentication, \"elasticsearch\", name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tfactory, err = es.NewFactory(ctx, *backend.Elasticsearch, telset, httpAuth)\n\tcase backend.Opensearch != nil:\n\t\tvar httpAuth extensionauth.HTTPClient\n\t\tif authResolver != nil {\n\t\t\thttpAuth, err = authResolver(backend.Opensearch.Authentication, \"opensearch\", name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tfactory, err = es.NewFactory(ctx, *backend.Opensearch, telset, httpAuth)\n\tcase backend.ClickHouse != nil:\n\t\tfactory, err = clickhouse.NewFactory(ctx, *backend.ClickHouse, telset)\n\tdefault:\n\t\terr = errors.New(\"empty configuration\")\n\t}\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize storage '%s': %w\", name, err)\n\t}\n\n\treturn factory, nil\n}\n"
  },
  {
    "path": "cmd/internal/storageconfig/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storageconfig\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/clickhousetest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/grpc\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nfunc getTelemetrySettings() telemetry.Settings {\n\treturn telemetry.NoopSettings()\n}\n\nfunc setupMockServer(t *testing.T, response []byte, statusCode int) *httptest.Server {\n\tmockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(statusCode)\n\t\tw.Write(response)\n\t}))\n\trequire.NotNil(t, mockServer)\n\tt.Cleanup(mockServer.Close)\n\treturn mockServer\n}\n\nfunc getVersionResponse(t *testing.T) []byte {\n\tversionResponse, e := json.Marshal(map[string]any{\n\t\t\"Version\": map[string]any{\n\t\t\t\"Number\": \"7\",\n\t\t},\n\t})\n\trequire.NoError(t, e)\n\treturn versionResponse\n}\n\nfunc TestCreateTraceStorageFactory_Memory(t *testing.T) {\n\tbackend := TraceBackend{\n\t\tMemory: &memory.Configuration{\n\t\t\tMaxTraces: 10000,\n\t\t},\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"memory-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_Badger(t *testing.T) {\n\tbackend := TraceBackend{\n\t\tBadger: &badger.Config{\n\t\t\tEphemeral:             true,\n\t\t\tMaintenanceInterval:   5,\n\t\t\tMetricsUpdateInterval: 10,\n\t\t},\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"badger-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_GRPC(t *testing.T) {\n\tbackend := TraceBackend{\n\t\tGRPC: &grpc.Config{\n\t\t\tClientConfig: configgrpc.ClientConfig{\n\t\t\t\tEndpoint: \"localhost:12345\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"grpc-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_Cassandra(t *testing.T) {\n\tbackend := TraceBackend{\n\t\tCassandra: &cassandra.Options{},\n\t}\n\n\t_, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"cassandra-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\t// Cassandra will fail without proper servers config, but we're testing the factory creation path\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"failed to initialize storage 'cassandra-test'\")\n}\n\nfunc TestCreateTraceStorageFactory_Elasticsearch(t *testing.T) {\n\tserver := setupMockServer(t, getVersionResponse(t), http.StatusOK)\n\tbackend := TraceBackend{\n\t\tElasticsearch: &escfg.Configuration{\n\t\t\tServers:  []string{server.URL},\n\t\t\tLogLevel: \"error\",\n\t\t},\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"es-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_ElasticsearchWithAuthResolver(t *testing.T) {\n\tserver := setupMockServer(t, getVersionResponse(t), http.StatusOK)\n\tbackend := TraceBackend{\n\t\tElasticsearch: &escfg.Configuration{\n\t\t\tServers:  []string{server.URL},\n\t\t\tLogLevel: \"error\",\n\t\t},\n\t}\n\n\tauthResolver := func(_ escfg.Authentication, _, _ string) (extensionauth.HTTPClient, error) {\n\t\treturn nil, nil // No auth needed for this test\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"es-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tauthResolver,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_ElasticsearchAuthResolverError(t *testing.T) {\n\tserver := setupMockServer(t, getVersionResponse(t), http.StatusOK)\n\tbackend := TraceBackend{\n\t\tElasticsearch: &escfg.Configuration{\n\t\t\tServers:  []string{server.URL},\n\t\t\tLogLevel: \"error\",\n\t\t},\n\t}\n\n\tauthResolver := func(_ escfg.Authentication, _, _ string) (extensionauth.HTTPClient, error) {\n\t\treturn nil, errors.New(\"auth error\")\n\t}\n\n\t_, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"es-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tauthResolver,\n\t)\n\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"auth error\")\n}\n\nfunc TestCreateTraceStorageFactory_Opensearch(t *testing.T) {\n\tserver := setupMockServer(t, getVersionResponse(t), http.StatusOK)\n\tbackend := TraceBackend{\n\t\tOpensearch: &escfg.Configuration{\n\t\t\tServers:  []string{server.URL},\n\t\t\tLogLevel: \"error\",\n\t\t},\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"os-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_OpensearchWithAuthResolver(t *testing.T) {\n\tserver := setupMockServer(t, getVersionResponse(t), http.StatusOK)\n\tbackend := TraceBackend{\n\t\tOpensearch: &escfg.Configuration{\n\t\t\tServers:  []string{server.URL},\n\t\t\tLogLevel: \"error\",\n\t\t},\n\t}\n\n\tauthResolver := func(_ escfg.Authentication, _, _ string) (extensionauth.HTTPClient, error) {\n\t\treturn nil, nil // No auth needed for this test\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"os-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tauthResolver,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_OpensearchAuthResolverError(t *testing.T) {\n\tserver := setupMockServer(t, getVersionResponse(t), http.StatusOK)\n\tbackend := TraceBackend{\n\t\tOpensearch: &escfg.Configuration{\n\t\t\tServers:  []string{server.URL},\n\t\t\tLogLevel: \"error\",\n\t\t},\n\t}\n\n\tauthResolver := func(_ escfg.Authentication, _, _ string) (extensionauth.HTTPClient, error) {\n\t\treturn nil, errors.New(\"auth error for opensearch\")\n\t}\n\n\t_, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"os-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tauthResolver,\n\t)\n\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"auth error for opensearch\")\n}\n\nfunc TestCreateTraceStorageFactory_ClickHouse(t *testing.T) {\n\ttestServer := clickhousetest.NewServer(clickhousetest.FailureConfig{})\n\tt.Cleanup(testServer.Close)\n\n\tbackend := TraceBackend{\n\t\tClickHouse: &clickhouse.Configuration{\n\t\t\tProtocol: \"http\",\n\t\t\tAddresses: []string{\n\t\t\t\ttestServer.Listener.Addr().String(),\n\t\t\t},\n\t\t},\n\t}\n\n\tfactory, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"clickhouse-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\tt.Cleanup(func() {\n\t\tif closer, ok := factory.(io.Closer); ok {\n\t\t\trequire.NoError(t, closer.Close())\n\t\t}\n\t})\n}\n\nfunc TestCreateTraceStorageFactory_ClickHouseError(t *testing.T) {\n\tbackend := TraceBackend{\n\t\tClickHouse: &clickhouse.Configuration{},\n\t}\n\n\t_, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"clickhouse-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\t// ClickHouse will fail without proper config, but we're testing the factory creation path\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"failed to initialize storage 'clickhouse-test'\")\n}\n\nfunc TestCreateTraceStorageFactory_EmptyBackend(t *testing.T) {\n\tbackend := TraceBackend{}\n\n\t_, err := CreateTraceStorageFactory(\n\t\tcontext.Background(),\n\t\t\"empty-test\",\n\t\tbackend,\n\t\tgetTelemetrySettings(),\n\t\tnil,\n\t)\n\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"failed to initialize storage 'empty-test'\")\n\trequire.Contains(t, err.Error(), \"empty configuration\")\n}\n"
  },
  {
    "path": "cmd/internal/storageconfig/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storageconfig\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/remote-storage/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nARG base_image\nARG debug_image\n\nARG SVC=remote-storage\n\nFROM $base_image AS release\nARG TARGETARCH\nARG USER_UID=10001\nCOPY remote-storage-linux-$TARGETARCH /go/bin/remote-storage-linux\nEXPOSE 16686/tcp\nENTRYPOINT [\"/go/bin/remote-storage-linux\"]\nUSER ${USER_UID}\n\nFROM $debug_image AS debug\nARG TARGETARCH=amd64\nARG USER_UID=10001\nCOPY remote-storage-debug-linux-$TARGETARCH /go/bin/remote-storage-linux\nEXPOSE 12345/tcp 16686/tcp\nENTRYPOINT [\"/go/bin/dlv\", \"exec\", \"/go/bin/remote-storage-linux\", \"--headless\", \"--listen=:12345\", \"--api-version=2\", \"--accept-multiclient\", \"--log\", \"--\"]\nUSER ${USER_UID}\n"
  },
  {
    "path": "cmd/remote-storage/README.md",
    "content": "# Jaeger Remote Storage\n\nThe `jaeger-remote-storage` binary allows sharing single-node storage implementations like memory or Badger over gRPC. It implements the Jaeger Remote Storage gRPC API, enabling Jaeger components to use these storage backends remotely.\n\n## Configuration\n\n### YAML Configuration\n\nConfigure remote-storage using a YAML configuration file with the `--config-file` flag:\n\n```bash\n./jaeger-remote-storage --config-file config.yaml\n```\n\n#### Configuration File Structure\n\n```yaml\n# Server configuration\ngrpc:\n  endpoint: :17271  # gRPC endpoint for remote storage API\n\n# Storage configuration\nstorage:\n  backends:\n    default-storage:\n      memory:\n        max_traces: 100000\n\n# Multi-tenancy configuration (optional)\nmulti_tenancy:\n  enabled: false\n```\n\n#### Storage Backends\n\nThe storage configuration follows the same format as the `jaeger_storage` extension in Jaeger v2. All official backends are supported.\n\n##### Memory Storage\n```yaml\nstorage:\n  backends:\n    memory-storage:\n      memory:\n        max_traces: 100000\n```\n\n##### Badger Storage\n```yaml\nstorage:\n  backends:\n    badger-storage:\n      badger:\n        directories:\n          keys: /tmp/jaeger/badger/keys\n          values: /tmp/jaeger/badger/values\n        ephemeral: false\n        ttl:\n          spans: 168h  # 7 days\n```\n\n##### gRPC Storage\n```yaml\nstorage:\n  backends:\n    grpc-storage:\n      grpc:\n        endpoint: remote-server:17271\n        tls:\n          insecure: true\n```\n\nSee example configuration files:\n- `config.yaml` - Memory storage example\n- `config-badger.yaml` - Badger storage example\n\n## Usage\n\n### Start with Memory Backend\n\n```bash\n./jaeger-remote-storage --config-file config.yaml\n```\n\n### Start with Badger Backend\n\n```bash\n./jaeger-remote-storage --config-file config-badger.yaml\n```\n\n### Multi-tenancy\n\nTo enable multi-tenancy:\n\n```yaml\ngrpc:\n  host-port: :17271\n\nmulti_tenancy:\n  enabled: true\n  header: x-tenant\n  tenants:\n    - tenant1\n    - tenant2\n\nstorage:\n  backends:\n    default-storage:\n      memory:\n        max_traces: 100000\n```\n\n## Integration with Jaeger\n\nTo use remote-storage with Jaeger components, configure them to use the gRPC storage backend:\n\n```yaml\nextensions:\n  jaeger_storage:\n    backends:\n      some-storage:\n        grpc:\n          endpoint: localhost:17271\n          tls:\n            insecure: true\n```\n\nFor more details, see the [gRPC storage documentation](../../internal/storage/v2/grpc/README.md).\n"
  },
  {
    "path": "cmd/remote-storage/app/config.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/config/confignet\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/internal/storageconfig\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\n// Config represents the configuration for remote-storage service.\ntype Config struct {\n\tGRPC    configgrpc.ServerConfig `mapstructure:\"grpc\"`\n\tTenancy tenancy.Options         `mapstructure:\"multi_tenancy\"`\n\t// This configuration is the same as of the main `jaeger` binary,\n\t// but only one backend should be defined.\n\tStorage storageconfig.Config `mapstructure:\"storage\"`\n}\n\n// LoadConfigFromViper loads the configuration from Viper.\nfunc LoadConfigFromViper(v *viper.Viper) (*Config, error) {\n\tcfg := &Config{}\n\n\t// Unmarshal the entire configuration\n\tif err := v.Unmarshal(cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal configuration: %w\", err)\n\t}\n\n\t// Validate storage configuration\n\tif err := cfg.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid configuration: %w\", err)\n\t}\n\n\treturn cfg, nil\n}\n\n// Validate validates the configuration.\nfunc (c *Config) Validate() error {\n\t// Validate storage configuration\n\tif err := c.Storage.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\t// Ensure only one backend is defined for remote-storage\n\tif len(c.Storage.TraceBackends) > 1 {\n\t\treturn fmt.Errorf(\"remote-storage only supports a single storage backend, but %d were configured\", len(c.Storage.TraceBackends))\n\t}\n\n\treturn nil\n}\n\n// GetStorageName returns the name of the first configured storage backend.\n// This is used as the default storage when not otherwise specified.\nfunc (c *Config) GetStorageName() string {\n\tfor name := range c.Storage.TraceBackends {\n\t\treturn name\n\t}\n\treturn \"\"\n}\n\n// DefaultConfig returns a default configuration with memory storage.\n// This is used when no configuration file is provided.\nfunc DefaultConfig() *Config {\n\treturn &Config{\n\t\tGRPC: configgrpc.ServerConfig{\n\t\t\tNetAddr: confignet.AddrConfig{\n\t\t\t\tEndpoint:  \":17271\",\n\t\t\t\tTransport: confignet.TransportTypeTCP,\n\t\t\t},\n\t\t},\n\t\tStorage: storageconfig.Config{\n\t\t\tTraceBackends: map[string]storageconfig.TraceBackend{\n\t\t\t\t\"memory\": {\n\t\t\t\t\tMemory: &memory.Configuration{\n\t\t\t\t\t\tMaxTraces: 1_000_000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "cmd/remote-storage/app/config_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLoadConfigFromViper(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tyamlConfig  string\n\t\texpectError string\n\t\tvalidate    func(*testing.T, *Config)\n\t}{\n\t\t{\n\t\t\tname: \"valid memory backend\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17271\nstorage:\n  backends:\n    default-storage:\n      memory:\n        max_traces: 50000\n`,\n\t\t\tvalidate: func(t *testing.T, cfg *Config) {\n\t\t\t\tassert.Equal(t, \":17271\", cfg.GRPC.NetAddr.Endpoint)\n\t\t\t\tassert.Len(t, cfg.Storage.TraceBackends, 1)\n\t\t\t\tassert.NotNil(t, cfg.Storage.TraceBackends[\"default-storage\"].Memory)\n\t\t\t\tassert.Equal(t, 50000, cfg.Storage.TraceBackends[\"default-storage\"].Memory.MaxTraces)\n\t\t\t\tassert.Equal(t, \"default-storage\", cfg.GetStorageName())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid memory backend\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17271\nstorage:\n  backends:\n    default-storage:\n      memory:\n        max_traces: NOT-A-NUMBER\n`,\n\t\t\texpectError: \"memory.max_traces\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid badger backend\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17272\nstorage:\n  backends:\n    badger-storage:\n      badger:\n        directories:\n          keys: /tmp/test-keys\n          values: /tmp/test-values\n        ephemeral: true\n`,\n\t\t\tvalidate: func(t *testing.T, cfg *Config) {\n\t\t\t\tassert.Equal(t, \":17272\", cfg.GRPC.NetAddr.Endpoint)\n\t\t\t\tassert.Len(t, cfg.Storage.TraceBackends, 1)\n\t\t\t\tassert.NotNil(t, cfg.Storage.TraceBackends[\"badger-storage\"].Badger)\n\t\t\t\tassert.Equal(t, \"badger-storage\", cfg.GetStorageName())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"missing storage backend\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17271\nstorage:\n  backends: {}\n`,\n\t\t\texpectError: \"at least one storage backend is required\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty backend configuration\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17271\nstorage:\n  backends:\n    empty-storage: {}\n`,\n\t\t\texpectError: \"at least one storage backend is required\",\n\t\t},\n\t\t{\n\t\t\tname: \"multiple backends should fail\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17271\nstorage:\n  backends:\n    memory-storage:\n      memory:\n        max_traces: 10000\n    another-storage:\n      memory:\n        max_traces: 20000\n`,\n\t\t\texpectError: \"remote-storage only supports a single storage backend\",\n\t\t},\n\t\t{\n\t\t\tname: \"with multi-tenancy enabled\",\n\t\t\tyamlConfig: `\ngrpc:\n  endpoint: :17271\nmulti_tenancy:\n  enabled: true\n  header: x-tenant\n  tenants:\n    - tenant1\n    - tenant2\nstorage:\n  backends:\n    default-storage:\n      memory:\n        max_traces: 10000\n`,\n\t\t\tvalidate: func(t *testing.T, cfg *Config) {\n\t\t\t\tassert.True(t, cfg.Tenancy.Enabled)\n\t\t\t\tassert.Equal(t, \"x-tenant\", cfg.Tenancy.Header)\n\t\t\t\tassert.Equal(t, []string{\"tenant1\", \"tenant2\"}, cfg.Tenancy.Tenants)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Create temporary config file\n\t\t\ttmpDir := t.TempDir()\n\t\t\tconfigFile := filepath.Join(tmpDir, \"config.yaml\")\n\t\t\terr := os.WriteFile(configFile, []byte(tt.yamlConfig), 0o600)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Load config with Viper\n\t\t\tv := viper.New()\n\t\t\tv.SetConfigFile(configFile)\n\t\t\terr = v.ReadInConfig()\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Load config from Viper\n\t\t\tcfg, err := LoadConfigFromViper(v)\n\n\t\t\tif tt.expectError != \"\" {\n\t\t\t\trequire.Contains(t, err.Error(), tt.expectError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, cfg)\n\t\t\t}\n\t\t\tif tt.validate != nil {\n\t\t\t\ttt.validate(t, cfg)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultConfig(t *testing.T) {\n\tcfg := DefaultConfig()\n\trequire.NotNil(t, cfg)\n\trequire.Equal(t, \":17271\", cfg.GRPC.NetAddr.Endpoint)\n\trequire.Len(t, cfg.Storage.TraceBackends, 1)\n\trequire.NotNil(t, cfg.Storage.TraceBackends[\"memory\"].Memory)\n\trequire.Equal(t, \"memory\", cfg.GetStorageName())\n}\n"
  },
  {
    "path": "cmd/remote-storage/app/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "cmd/remote-storage/app/server.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\n\t\"go.opentelemetry.io/collector/component\"\n\t\"go.opentelemetry.io/collector/component/componentstatus\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/config/confignet\"\n\t\"go.uber.org/zap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/health\"\n\t\"google.golang.org/grpc/health/grpc_health_v1\"\n\t\"google.golang.org/grpc/reflection\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tgrpcstorage \"github.com/jaegertracing/jaeger/internal/storage/v2/grpc\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\n// Server runs a gRPC server\ntype Server struct {\n\tgrpcCfg    configgrpc.ServerConfig\n\tgrpcConn   net.Listener\n\tgrpcServer *grpc.Server\n\tstopped    sync.WaitGroup\n\ttelset     telemetry.Settings\n}\n\n// NewServer creates and initializes Server.\nfunc NewServer(\n\tctx context.Context,\n\tgrpcCfg configgrpc.ServerConfig,\n\tts tracestore.Factory,\n\tds depstore.Factory,\n\ttm *tenancy.Manager,\n\ttelset telemetry.Settings,\n) (*Server, error) {\n\treader, err := ts.CreateTraceReader()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\twriter, err := ts.CreateTraceWriter()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdepReader, err := ds.CreateDependencyReader()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// This is required because we are using the config to start the server.\n\t// If the config is created manually (e.g. in tests), the transport might not be set.\n\tgrpcCfg.NetAddr.Transport = confignet.TransportTypeTCP\n\n\tv2Handler := grpcstorage.NewHandler(reader, writer, depReader)\n\n\tgrpcServer, err := createGRPCServer(ctx, grpcCfg, tm, v2Handler, telset)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Server{\n\t\tgrpcCfg:    grpcCfg,\n\t\tgrpcServer: grpcServer,\n\t\ttelset:     telset,\n\t}, nil\n}\n\nfunc createGRPCServer(\n\tctx context.Context,\n\tcfg configgrpc.ServerConfig,\n\ttm *tenancy.Manager,\n\tv2Handler *grpcstorage.Handler,\n\ttelset telemetry.Settings,\n) (*grpc.Server, error) {\n\tunaryInterceptors := []grpc.UnaryServerInterceptor{\n\t\tbearertoken.NewUnaryServerInterceptor(),\n\t}\n\tstreamInterceptors := []grpc.StreamServerInterceptor{\n\t\tbearertoken.NewStreamServerInterceptor(),\n\t}\n\t//nolint:contextcheck // The context is handled by the interceptors\n\tif tm.Enabled {\n\t\tunaryInterceptors = append(unaryInterceptors, tenancy.NewGuardingUnaryInterceptor(tm))\n\t\tstreamInterceptors = append(streamInterceptors, tenancy.NewGuardingStreamInterceptor(tm))\n\t}\n\n\tcfg.NetAddr.Transport = confignet.TransportTypeTCP\n\tvar extensions map[component.ID]component.Component\n\tif telset.Host != nil {\n\t\textensions = telset.Host.GetExtensions()\n\t}\n\tserver, err := cfg.ToServer(ctx,\n\t\textensions,\n\t\ttelset.ToOtelComponent(),\n\t\tconfiggrpc.WithGrpcServerOption(grpc.ChainUnaryInterceptor(unaryInterceptors...)),\n\t\tconfiggrpc.WithGrpcServerOption(grpc.ChainStreamInterceptor(streamInterceptors...)),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create gRPC server: %w\", err)\n\t}\n\thealthServer := health.NewServer()\n\treflection.Register(server)\n\n\tv2Handler.Register(server, healthServer)\n\tgrpc_health_v1.RegisterHealthServer(server, healthServer)\n\n\treturn server, nil\n}\n\n// Start gRPC server concurrently\nfunc (s *Server) Start(ctx context.Context) error {\n\tvar err error\n\ts.grpcConn, err = s.grpcCfg.NetAddr.Listen(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to listen on gRPC port: %w\", err)\n\t}\n\ts.telset.Logger.Info(\"Starting GRPC server\", zap.Stringer(\"addr\", s.grpcConn.Addr()))\n\ts.stopped.Go(func() {\n\t\tif err := s.grpcServer.Serve(s.grpcConn); err != nil {\n\t\t\ts.telset.Logger.Error(\"GRPC server exited\", zap.Error(err))\n\t\t\ts.telset.ReportStatus(componentstatus.NewFatalErrorEvent(err))\n\t\t}\n\t})\n\n\treturn nil\n}\n\n// Close stops http, GRPC servers and closes the port listener.\nfunc (s *Server) Close() error {\n\ts.grpcServer.Stop()\n\ts.stopped.Wait()\n\ts.telset.ReportStatus(componentstatus.NewEvent(componentstatus.StatusStopped))\n\treturn nil\n}\n\nfunc (s *Server) GRPCAddr() string {\n\treturn s.grpcConn.Addr().String()\n}\n"
  },
  {
    "path": "cmd/remote-storage/app/server_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage app\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/config/confignet\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest/observer\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/jaegertracing/jaeger/internal/grpctest\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\ttracestoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\nvar testCertKeyLocation = \"../../../internal/config/tlscfg/testdata\"\n\nfunc TestNewServer_CreateStorageErrors(t *testing.T) {\n\tcreateServer := func(factory *fakeFactory) (*Server, error) {\n\t\treturn NewServer(\n\t\t\tcontext.Background(),\n\t\t\tconfiggrpc.ServerConfig{\n\t\t\t\tNetAddr: confignet.AddrConfig{\n\t\t\t\t\tEndpoint: \":0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfactory,\n\t\t\tfactory,\n\t\t\ttenancy.NewManager(&tenancy.Options{}),\n\t\t\ttelemetry.NoopSettings(),\n\t\t)\n\t}\n\n\tfactory := &fakeFactory{readerErr: errors.New(\"no reader\")}\n\t_, err := createServer(factory)\n\trequire.ErrorContains(t, err, \"no reader\")\n\n\tfactory = &fakeFactory{writerErr: errors.New(\"no writer\")}\n\t_, err = createServer(factory)\n\trequire.ErrorContains(t, err, \"no writer\")\n\n\tfactory = &fakeFactory{depReaderErr: errors.New(\"no deps\")}\n\t_, err = createServer(factory)\n\trequire.ErrorContains(t, err, \"no deps\")\n\n\tfactory = &fakeFactory{}\n\ts, err := createServer(factory)\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.Start(context.Background()))\n\tvalidateGRPCServer(t, s.GRPCAddr())\n\trequire.NoError(t, s.grpcConn.Close())\n}\n\nfunc TestServerStart_BadPortErrors(t *testing.T) {\n\tsrv := &Server{\n\t\tgrpcCfg: configgrpc.ServerConfig{\n\t\t\tNetAddr: confignet.AddrConfig{\n\t\t\t\tEndpoint: \":-1\",\n\t\t\t},\n\t\t},\n\t}\n\trequire.Error(t, srv.Start(context.Background()))\n}\n\ntype fakeFactory struct {\n\treader    tracestore.Reader\n\twriter    tracestore.Writer\n\tdepReader depstore.Reader\n\n\treaderErr    error\n\twriterErr    error\n\tdepReaderErr error\n}\n\nfunc (f *fakeFactory) CreateTraceReader() (tracestore.Reader, error) {\n\tif f.readerErr != nil {\n\t\treturn nil, f.readerErr\n\t}\n\treturn f.reader, nil\n}\n\nfunc (f *fakeFactory) CreateTraceWriter() (tracestore.Writer, error) {\n\tif f.writerErr != nil {\n\t\treturn nil, f.writerErr\n\t}\n\treturn f.writer, nil\n}\n\nfunc (f *fakeFactory) CreateDependencyReader() (depstore.Reader, error) {\n\tif f.depReaderErr != nil {\n\t\treturn nil, f.depReaderErr\n\t}\n\treturn f.depReader, nil\n}\n\nfunc (*fakeFactory) InitArchiveStorage(*zap.Logger) (spanstore.Reader, spanstore.Writer) {\n\treturn nil, nil\n}\n\nfunc TestNewServer_TLSConfigError(t *testing.T) {\n\ttlsCfg := configtls.ServerConfig{\n\t\tClientCAFile: \"invalid/path\",\n\t\tConfig: configtls.Config{\n\t\t\tCertFile: \"invalid/path\",\n\t\t\tKeyFile:  \"invalid/path\",\n\t\t},\n\t}\n\ttelset := telemetry.NoopSettings()\n\ttelset.Logger = zap.NewNop()\n\n\t_, err := NewServer(\n\t\tcontext.Background(),\n\t\tconfiggrpc.ServerConfig{\n\t\t\tNetAddr: confignet.AddrConfig{\n\t\t\t\tEndpoint: \":8081\",\n\t\t\t},\n\t\t\tTLS: configoptional.Some(tlsCfg),\n\t\t},\n\t\t&fakeFactory{},\n\t\t&fakeFactory{},\n\t\ttenancy.NewManager(&tenancy.Options{}),\n\t\ttelset,\n\t)\n\tassert.ErrorContains(t, err, \"failed to load TLS config\")\n}\n\nvar testCases = []struct {\n\tname              string\n\tTLS               *configtls.ServerConfig\n\tclientTLS         *configtls.ClientConfig\n\texpectError       bool\n\texpectClientError bool\n\texpectServerFail  bool\n}{\n\t{\n\t\tname: \"should pass with insecure connection\",\n\t\tTLS:  nil,\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tInsecure: true,\n\t\t},\n\t\texpectError:       false,\n\t\texpectClientError: false,\n\t\texpectServerFail:  false,\n\t},\n\t{\n\t\tname: \"should fail with TLS client to untrusted TLS server\",\n\t\tTLS: &configtls.ServerConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t},\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\texpectError:       true,\n\t\texpectClientError: true,\n\t\texpectServerFail:  false,\n\t},\n\t{\n\t\tname: \"should fail with TLS client to trusted TLS server with incorrect hostname\",\n\t\tTLS: &configtls.ServerConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t},\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCAFile: testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t\t},\n\t\t\tServerName: \"nonEmpty\",\n\t\t},\n\t\texpectError:       true,\n\t\texpectClientError: true,\n\t\texpectServerFail:  false,\n\t},\n\t{\n\t\tname: \"should pass with TLS client to trusted TLS server with correct hostname\",\n\t\tTLS: &configtls.ServerConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t},\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCAFile: testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t\t},\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\texpectError:       false,\n\t\texpectClientError: false,\n\t\texpectServerFail:  false,\n\t},\n\t{\n\t\tname: \"should fail with TLS client without cert to trusted TLS server requiring cert\",\n\t\tTLS: &configtls.ServerConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t\tClientCAFile: testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t},\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCAFile: testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t\t},\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\texpectError:       false,\n\t\texpectServerFail:  false,\n\t\texpectClientError: true,\n\t},\n\t{\n\t\tname: \"should pass with TLS client with cert to trusted TLS server requiring cert\",\n\t\tTLS: &configtls.ServerConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t\tClientCAFile: testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t},\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCAFile:   testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-client-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-client-key.pem\",\n\t\t\t},\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\texpectError:       false,\n\t\texpectServerFail:  false,\n\t\texpectClientError: false,\n\t},\n\t{\n\t\tname: \"should fail with TLS client without cert to trusted TLS server requiring cert from a different CA\",\n\t\tTLS: &configtls.ServerConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-server-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-server-key.pem\",\n\t\t\t},\n\t\t\tClientCAFile: testCertKeyLocation + \"/wrong-CA-cert.pem\",\n\t\t},\n\t\tclientTLS: &configtls.ClientConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCAFile:   testCertKeyLocation + \"/example-CA-cert.pem\",\n\t\t\t\tCertFile: testCertKeyLocation + \"/example-client-cert.pem\",\n\t\t\t\tKeyFile:  testCertKeyLocation + \"/example-client-key.pem\",\n\t\t\t},\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\texpectError:       false,\n\t\texpectServerFail:  false,\n\t\texpectClientError: true,\n\t},\n}\n\ntype grpcClient struct {\n\tstorage.TraceReaderClient\n\n\tconn *grpc.ClientConn\n}\n\nfunc newGRPCClient(t *testing.T, addr string, creds credentials.TransportCredentials, tm *tenancy.Manager) *grpcClient {\n\tdialOpts := []grpc.DialOption{\n\t\tgrpc.WithUnaryInterceptor(tenancy.NewClientUnaryInterceptor(tm)),\n\t}\n\tif creds != nil {\n\t\tdialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))\n\t} else {\n\t\tdialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\t}\n\tconn, err := grpc.NewClient(addr, dialOpts...)\n\trequire.NoError(t, err)\n\n\treturn &grpcClient{\n\t\tTraceReaderClient: storage.NewTraceReaderClient(conn),\n\t\tconn:              conn,\n\t}\n}\n\nfunc TestServerGRPCTLS(t *testing.T) {\n\tfor _, test := range testCases {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttls := configoptional.None[configtls.ServerConfig]()\n\t\t\tif test.TLS != nil {\n\t\t\t\ttls = configoptional.Some(*test.TLS)\n\t\t\t}\n\t\t\tserverOptions := configgrpc.ServerConfig{\n\t\t\t\tNetAddr: confignet.AddrConfig{\n\t\t\t\t\tEndpoint: \":0\",\n\t\t\t\t},\n\t\t\t\tTLS: tls,\n\t\t\t}\n\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\tf := &fakeFactory{\n\t\t\t\treader: reader,\n\t\t\t}\n\t\t\texpectedServices := []string{\"test\"}\n\t\t\treader.On(\"GetServices\", mock.AnythingOfType(\"*context.valueCtx\")).Return(expectedServices, nil)\n\n\t\t\ttm := tenancy.NewManager(&tenancy.Options{Enabled: true})\n\t\t\ttelset := telemetry.NoopSettings()\n\t\t\ttelset.Logger = zap.NewNop()\n\t\t\tserver, err := NewServer(\n\t\t\t\tcontext.Background(),\n\t\t\t\tserverOptions,\n\t\t\t\tf,\n\t\t\t\tf,\n\t\t\t\ttm,\n\t\t\t\ttelset,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NoError(t, server.Start(context.Background()))\n\n\t\t\tvar clientError error\n\t\t\tvar client *grpcClient\n\n\t\t\tif serverOptions.TLS.HasValue() {\n\t\t\t\tclientTLSCfg, err0 := test.clientTLS.LoadTLSConfig(context.Background())\n\t\t\t\trequire.NoError(t, err0)\n\t\t\t\tcreds := credentials.NewTLS(clientTLSCfg)\n\t\t\t\tclient = newGRPCClient(t, server.GRPCAddr(), creds, tm)\n\t\t\t} else {\n\t\t\t\tclient = newGRPCClient(t, server.GRPCAddr(), nil, tm)\n\t\t\t}\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tctx = tenancy.WithTenant(ctx, \"foo\")\n\t\t\tres, clientError := client.GetServices(ctx, &storage.GetServicesRequest{})\n\n\t\t\tif test.expectClientError {\n\t\t\t\trequire.Error(t, clientError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, clientError)\n\t\t\t\tassert.Equal(t, expectedServices, res.Services)\n\t\t\t}\n\t\t\trequire.NoError(t, client.conn.Close())\n\t\t\tserver.Close()\n\t\t})\n\t}\n}\n\nfunc TestServerHandlesPortZero(t *testing.T) {\n\tzapCore, logs := observer.New(zap.InfoLevel)\n\tlogger := zap.New(zapCore)\n\ttelset := telemetry.NoopSettings()\n\ttelset.Logger = logger\n\tserver, err := NewServer(\n\t\tcontext.Background(),\n\t\tconfiggrpc.ServerConfig{\n\t\t\tNetAddr: confignet.AddrConfig{Endpoint: \":0\"},\n\t\t},\n\t\t&fakeFactory{},\n\t\t&fakeFactory{},\n\t\ttenancy.NewManager(&tenancy.Options{}),\n\t\ttelset,\n\t)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, server.Start(context.Background()))\n\n\tconst line = \"Starting GRPC server\"\n\tmessage := logs.FilterMessage(line)\n\trequire.Equal(t, 1, message.Len(), \"Expected '%s' log message, actual logs: %+v\", line, logs)\n\n\tonlyEntry := message.All()[0]\n\thostPort := onlyEntry.ContextMap()[\"addr\"].(string)\n\tvalidateGRPCServer(t, hostPort)\n\n\tserver.Close()\n}\n\nfunc validateGRPCServer(t *testing.T, hostPort string) {\n\tgrpctest.ReflectionServiceValidator{\n\t\tHostPort: hostPort,\n\t\tExpectedServices: []string{\n\t\t\t// writer\n\t\t\t\"opentelemetry.proto.collector.trace.v1.TraceService\",\n\t\t\t// reader\n\t\t\t\"jaeger.storage.v2.TraceReader\",\n\t\t\t\"jaeger.storage.v2.DependencyReader\",\n\t\t\t// health\n\t\t\t\"grpc.health.v1.Health\",\n\t\t},\n\t}.Execute(t)\n}\n"
  },
  {
    "path": "cmd/remote-storage/config-badger.yaml",
    "content": "# Example configuration for remote-storage service with Badger backend\n\n# Server configuration\ngrpc:\n  # Host:Port to listen on\n  endpoint: :17271\n\n\n# Storage configuration - Badger backend\nstorage:\n  backends:\n    badger-storage:\n      badger:\n        directories:\n          keys: /tmp/jaeger/badger/keys\n          values: /tmp/jaeger/badger/values\n        ephemeral: false\n        maintenance_interval: 5m\n        metrics_update_interval: 10s\n        ttl:\n          spans: 168h  # 7 days\n\n# Multi-tenancy configuration (optional)\nmulti_tenancy:\n  enabled: false\n"
  },
  {
    "path": "cmd/remote-storage/config.yaml",
    "content": "# Example configuration for remote-storage service\n# This service exposes a gRPC API for remote storage backends\n# and allows sharing single-node storage implementations like memory or Badger.\n\n# Server configuration\ngrpc:\n  # Host:Port to listen on\n  endpoint: :17271\n\n\n# Storage configuration using the same format as jaeger v2\nstorage:\n  backends:\n    default-storage:\n      memory:\n        max_traces: 100000\n\n# Multi-tenancy configuration (optional)\nmulti_tenancy:\n  enabled: false\n  # header: x-tenant\n  # tenants:\n  #   - tenant1\n  #   - tenant2\n"
  },
  {
    "path": "cmd/remote-storage/main.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/otel/metric/noop\"\n\t_ \"go.uber.org/automaxprocs\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/internal/docs\"\n\t\"github.com/jaegertracing/jaeger/cmd/internal/featuregate\"\n\t\"github.com/jaegertracing/jaeger/cmd/internal/flags\"\n\t\"github.com/jaegertracing/jaeger/cmd/internal/printconfig\"\n\t\"github.com/jaegertracing/jaeger/cmd/internal/status\"\n\t\"github.com/jaegertracing/jaeger/cmd/internal/storageconfig\"\n\t\"github.com/jaegertracing/jaeger/cmd/remote-storage/app\"\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n\t\"github.com/jaegertracing/jaeger/internal/version\"\n\t\"github.com/jaegertracing/jaeger/ports\"\n)\n\nconst serviceName = \"jaeger-remote-storage\"\n\n// loadConfig loads configuration from viper, or returns default configuration if no config file is provided.\nfunc loadConfig(v *viper.Viper, logger *zap.Logger) (*app.Config, error) {\n\t// If viper config is not provided, use defaults\n\tif v.ConfigFileUsed() == \"\" {\n\t\tlogger.Info(\"No configuration file provided, using default configuration (memory storage on :17271)\")\n\t\treturn app.DefaultConfig(), nil\n\t}\n\n\treturn app.LoadConfigFromViper(v)\n}\n\nfunc main() {\n\tsvc := flags.NewService(ports.RemoteStorageAdminHTTP)\n\n\tv := viper.New()\n\tcommand := &cobra.Command{\n\t\tUse:   serviceName,\n\t\tShort: serviceName + \" allows sharing single-node storage implementations like memstore or Badger.\",\n\t\tLong:  serviceName + ` allows sharing single-node storage implementations like memstore or Badger. It implements Jaeger Remote Storage gRPC API.`,\n\t\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\t\tif err := svc.Start(v); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlogger := svc.Logger\n\t\t\tbaseFactory := svc.MetricsFactory.Namespace(metrics.NSOptions{Name: \"jaeger\"})\n\t\t\tmetricsFactory := baseFactory.Namespace(metrics.NSOptions{Name: \"remote-storage\"})\n\t\t\tversion.NewInfoMetrics(metricsFactory)\n\n\t\t\t// Load configuration from YAML file, or use defaults if not provided\n\t\t\tcfg, err := loadConfig(v, logger)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Fatal(\"Failed to load configuration\", zap.Error(err))\n\t\t\t}\n\n\t\t\tbaseTelset := telemetry.Settings{\n\t\t\t\tLogger:        svc.Logger,\n\t\t\t\tMetrics:       baseFactory,\n\t\t\t\tHost:          svc.Admin.Host(),\n\t\t\t\tMeterProvider: noop.NewMeterProvider(),\n\t\t\t}\n\n\t\t\ttm := tenancy.NewManager(&cfg.Tenancy)\n\t\t\ttelset := baseTelset\n\t\t\ttelset.Metrics = metricsFactory\n\n\t\t\t// Get the storage name (first backend configured)\n\t\t\tstorageName := cfg.GetStorageName()\n\t\t\tif storageName == \"\" {\n\t\t\t\tlogger.Fatal(\"No storage backend configured\")\n\t\t\t}\n\n\t\t\t// Get the backend configuration\n\t\t\tbackend, ok := cfg.Storage.TraceBackends[storageName]\n\t\t\tif !ok {\n\t\t\t\tlogger.Fatal(\"Storage backend not found\", zap.String(\"name\", storageName))\n\t\t\t}\n\n\t\t\t// Create storage factory from configuration (no auth resolver for remote-storage)\n\t\t\ttraceFactory, err := storageconfig.CreateTraceStorageFactory(\n\t\t\t\tcontext.Background(),\n\t\t\t\tstorageName,\n\t\t\t\tbackend,\n\t\t\t\ttelset,\n\t\t\t\tnil, // no auth resolver for remote-storage\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Fatal(\"Failed to create storage factory\", zap.Error(err))\n\t\t\t}\n\n\t\t\tdepFactory, ok := traceFactory.(depstore.Factory)\n\t\t\tif !ok {\n\t\t\t\tlogger.Fatal(\"Storage does not implement dependency store\", zap.String(\"name\", storageName))\n\t\t\t}\n\n\t\t\t// Create and start server\n\t\t\tserver, err := app.NewServer(\n\t\t\t\tcontext.Background(),\n\t\t\t\tcfg.GRPC,\n\t\t\t\ttraceFactory,\n\t\t\t\tdepFactory,\n\t\t\t\ttm,\n\t\t\t\ttelset,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Fatal(\"Failed to create server\", zap.Error(err))\n\t\t\t}\n\n\t\t\tif err := server.Start(context.Background()); err != nil {\n\t\t\t\tlogger.Fatal(\"Could not start servers\", zap.Error(err))\n\t\t\t}\n\n\t\t\treturn svc.RunAndThen(func() {\n\t\t\t\tserver.Close()\n\t\t\t\tif closer, ok := traceFactory.(io.Closer); ok {\n\t\t\t\t\tif err := closer.Close(); err != nil {\n\t\t\t\t\t\tlogger.Error(\"Failed to close storage factory\", zap.Error(err))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t}\n\n\tcommand.AddCommand(version.Command())\n\tcommand.AddCommand(docs.Command(v))\n\tcommand.AddCommand(status.Command(v, ports.RemoteStorageAdminHTTP))\n\tcommand.AddCommand(printconfig.Command(v))\n\tcommand.AddCommand(featuregate.Command())\n\n\t// Add only basic flags (not storage flags)\n\tconfig.AddFlags(\n\t\tv,\n\t\tcommand,\n\t\tsvc.AddFlags,\n\t)\n\n\tif err := command.Execute(); err != nil {\n\t\tfmt.Println(err.Error())\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/tracegen/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nFROM scratch\nARG TARGETARCH\nARG USER_UID=10001\n\nCOPY tracegen-linux-$TARGETARCH /go/bin/tracegen-linux\nENTRYPOINT [\"/go/bin/tracegen-linux\"]\nUSER ${USER_UID}\n"
  },
  {
    "path": "cmd/tracegen/README.md",
    "content": "# tracegen\n\n`tracegen` is a utility that can generate a steady flow of simple traces useful for performance tuning.\nTraces are produced concurrently from one or more worker goroutines. Run with `-h` to see all cli flags.\n\nThe binary is available from the Releases page, as well as a Docker image:\n\n```sh\n$ docker run jaegertracing/jaeger-tracegen -service abcd -traces 10\n```\n\nThe generator can be configured to export traces in different formats, via `-exporter` flag.\nBy default, the exporters send data to `localhost`. If running in a container, this refers\nto the networking namespace of the container itself, so to export to another container,\nthe exporters need to be provided with appropriate location.\nOTLP exporter accepts configuration via environment variables.\nFor more information about configuring OTLP exporter, see [OpenTelemetry Protocol Exporter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md).\n\nSee example in the included [docker-compose](./docker-compose.yml) file.\n"
  },
  {
    "path": "cmd/tracegen/docker-compose.yml",
    "content": "services:\n    jaeger:\n      image: cr.jaegertracing.io/jaegertracing/jaeger:2.15.1@sha256:a7dd965687d45507072676db81e6903706ba334dfc92f0c248125cfd9a70c483\n      ports:\n        - '16686:16686'\n        - '4318:4318'\n\n    tracegen:\n      image: cr.jaegertracing.io/jaegertracing/jaeger-tracegen:2.15.1@sha256:8149733c9c54c2b272d2141388aa4f0ae95704c814df976b434fba162752a235\n      environment:\n        - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318\n      command: [\"-duration\", \"10s\", \"-workers\", \"3\", \"-pause\", \"250ms\"]\n      depends_on:\n        - jaeger\n"
  },
  {
    "path": "cmd/tracegen/main.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/go-logr/zapr\"\n\t\"go.opentelemetry.io/contrib/samplers/jaegerremote\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp\"\n\t\"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\t\"go.opentelemetry.io/otel/sdk/resource\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jaegerclientenv2otel\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n\t\"github.com/jaegertracing/jaeger/internal/tracegen\"\n\t\"github.com/jaegertracing/jaeger/internal/version\"\n)\n\nvar flagAdaptiveSamplingEndpoint string\n\nfunc main() {\n\tzc := zap.NewDevelopmentConfig()\n\tzc.Level = zap.NewAtomicLevelAt(zapcore.Level(-8)) // level used by OTEL's Debug()\n\tlogger, err := zc.Build()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\totel.SetLogger(zapr.NewLogger(logger))\n\n\tfs := flag.CommandLine\n\tcfg := new(tracegen.Config)\n\tcfg.Flags(fs)\n\tfs.StringVar(\n\t\t&flagAdaptiveSamplingEndpoint,\n\t\t\"adaptive-sampling\",\n\t\t\"\",\n\t\t\"HTTP endpoint to use to retrieve sampling strategies, \"+\n\t\t\t\"e.g. http://localhost:14268/api/sampling. \"+\n\t\t\t\"When not specified a standard SDK sampler will be used \"+\n\t\t\t\"(see OTEL_TRACES_SAMPLER env var in OTEL docs)\")\n\tflag.Parse()\n\n\tlogger.Info(version.Get().String())\n\n\totel.SetTextMapPropagator(propagation.TraceContext{})\n\tjaegerclientenv2otel.MapJaegerToOtelEnvVars(logger)\n\n\ttracers, shutdown := createTracers(cfg, logger)\n\tdefer shutdown(context.Background())\n\n\ttracegen.Run(cfg, tracers, logger)\n}\n\nfunc createTracers(cfg *tracegen.Config, logger *zap.Logger) ([]trace.Tracer, func(context.Context) error) {\n\tif cfg.Services < 1 {\n\t\tcfg.Services = 1\n\t}\n\tvar shutdown []func(context.Context) error\n\tvar tracers []trace.Tracer\n\tfor s := 0; s < cfg.Services; s++ {\n\t\tsvc := cfg.Service\n\t\tif cfg.Services > 1 {\n\t\t\tsvc = fmt.Sprintf(\"%s-%02d\", svc, s)\n\t\t}\n\n\t\texp, err := createOtelExporter(cfg.TraceExporter)\n\t\tif err != nil {\n\t\t\tlogger.Sugar().Fatalf(\"cannot create trace exporter %s: %s\", cfg.TraceExporter, err)\n\t\t}\n\t\tlogger.Sugar().Infof(\"using %s trace exporter for service %s\", cfg.TraceExporter, svc)\n\n\t\tres, err := resource.New(\n\t\t\tcontext.Background(),\n\t\t\tresource.WithSchemaURL(otelsemconv.SchemaURL),\n\t\t\tresource.WithAttributes(otelsemconv.ServiceNameAttribute(svc)),\n\t\t\tresource.WithTelemetrySDK(),\n\t\t\tresource.WithHost(),\n\t\t\tresource.WithOSType(),\n\t\t)\n\t\tif err != nil {\n\t\t\tlogger.Sugar().Fatalf(\"resource creation failed: %s\", err)\n\t\t}\n\n\t\topts := []sdktrace.TracerProviderOption{\n\t\t\tsdktrace.WithBatcher(exp, sdktrace.WithBlocking()),\n\t\t\tsdktrace.WithResource(res),\n\t\t}\n\t\tif flagAdaptiveSamplingEndpoint != \"\" {\n\t\t\tjaegerRemoteSampler := jaegerremote.New(\n\t\t\t\tsvc,\n\t\t\t\tjaegerremote.WithSamplingServerURL(flagAdaptiveSamplingEndpoint),\n\t\t\t\tjaegerremote.WithSamplingRefreshInterval(5*time.Second),\n\t\t\t\tjaegerremote.WithInitialSampler(sdktrace.TraceIDRatioBased(0.5)),\n\t\t\t)\n\t\t\topts = append(opts, sdktrace.WithSampler(jaegerRemoteSampler))\n\t\t\tlogger.Sugar().Infof(\"using adaptive sampling URL: %s\", flagAdaptiveSamplingEndpoint)\n\t\t}\n\t\ttp := sdktrace.NewTracerProvider(opts...)\n\t\ttracers = append(tracers, tp.Tracer(cfg.Service))\n\t\tshutdown = append(shutdown, tp.Shutdown)\n\t}\n\treturn tracers, func(ctx context.Context) error {\n\t\tvar errs []error\n\t\tfor _, f := range shutdown {\n\t\t\terrs = append(errs, f(ctx))\n\t\t}\n\t\treturn errors.Join(errs...)\n\t}\n}\n\nfunc createOtelExporter(exporterType string) (sdktrace.SpanExporter, error) {\n\tvar exporter sdktrace.SpanExporter\n\tvar err error\n\tswitch exporterType {\n\tcase \"jaeger\":\n\t\treturn nil, errors.New(\"jaeger exporter is no longer supported, please use otlp\")\n\tcase \"otlp\", \"otlp-http\":\n\t\tclient := otlptracehttp.NewClient(\n\t\t\totlptracehttp.WithInsecure(),\n\t\t)\n\t\texporter, err = otlptrace.New(context.Background(), client)\n\tcase \"otlp-grpc\":\n\t\tclient := otlptracegrpc.NewClient(\n\t\t\totlptracegrpc.WithInsecure(),\n\t\t)\n\t\texporter, err = otlptrace.New(context.Background(), client)\n\tcase \"stdout\":\n\t\texporter, err = stdouttrace.New()\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unrecognized exporter type %s\", exporterType)\n\t}\n\treturn exporter, err\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Do not delete this file, it's needed for `go get` to work.\n\n// Package jaeger contains the code for Jaeger backend.\npackage jaeger\n"
  },
  {
    "path": "docker-compose/cassandra/v4/docker-compose.yaml",
    "content": "services:\n  cassandra:\n    image: cassandra:4.1@sha256:b9451ebdfa53f9e22b470e1420f2a94a3433738b7f25350472d3443f0b203b75\n    container_name: \"cassandra-4\"\n    ports:\n      - \"9042:9042\"\n      - \"9160:9160\"\n    # We enable password authentication that defaults to cassandra/cassandra superuser / pwd.\n    # https://cassandra.apache.org/doc/stable/cassandra/operating/security.html#authentication\n    command: >\n      /bin/sh -c \"echo 'authenticator: PasswordAuthenticator' >> /etc/cassandra/cassandra.yaml && docker-entrypoint.sh cassandra -f\"\n    environment:\n      - CASSANDRA_DC=dc1\n      - CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch\n    networks:\n      - cassandra-net\n    healthcheck:\n      test: [\"CMD\", \"cqlsh\", \"-u\", \"cassandra\", \"-p\", \"cassandra\", \"-e\", \"describe keyspaces\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n\nnetworks:\n  cassandra-net:\n    driver: bridge\n"
  },
  {
    "path": "docker-compose/cassandra/v5/docker-compose.yaml",
    "content": "services:\n  cassandra:\n    image: cassandra:5.0@sha256:70b40a2025d450f7865c5ec6f1ebea13108166f81fe41462069690cb4d9690f2\n    container_name: \"cassandra-5\"\n    ports:\n      - \"9042:9042\"\n      - \"9160:9160\"\n    # We enable password authentication that defaults to cassandra/cassandra superuser / pwd.\n    # https://cassandra.apache.org/doc/stable/cassandra/operating/security.html#authentication\n    command: >\n      /bin/sh -c \"echo 'authenticator: PasswordAuthenticator' >> /etc/cassandra/cassandra.yaml && docker-entrypoint.sh cassandra -f\"\n    environment:\n      - CASSANDRA_DC=dc1\n      - CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch\n    networks:\n      - cassandra-net\n    healthcheck:\n      test: [\"CMD\", \"cqlsh\", \"-u\", \"cassandra\", \"-p\", \"cassandra\", \"-e\", \"describe keyspaces\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n\nnetworks:\n  cassandra-net:\n    driver: bridge\n"
  },
  {
    "path": "docker-compose/clickhouse/docker-compose.yml",
    "content": "services:\n  clickhouse:\n    image: clickhouse/clickhouse-server:25.12.1@sha256:7234d193fbeb3a375f21f0bb99040396f68bb2e82cada4f4947d7fcf9638bbf3\n    container_name: clickhouse\n    environment:\n      - CLICKHOUSE_USER=default\n      - CLICKHOUSE_PASSWORD=password\n      - CLICKHOUSE_DB=jaeger\n    ports:\n      - \"8123:8123\"\n      - \"9000:9000\"\n    healthcheck:\n      test: [\"CMD\", \"clickhouse-client\", \"--query=SELECT 1\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n"
  },
  {
    "path": "docker-compose/elasticsearch/v6/docker-compose.yml",
    "content": "services:\n  elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.23@sha256:740c3614289539e9782b8a3c4de5100599bbec669d6c56bc21a76eea661e80a5\n    environment:\n      - discovery.type=single-node\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n    ports:\n      - \"9200:9200\""
  },
  {
    "path": "docker-compose/elasticsearch/v7/docker-compose.yml",
    "content": "services:\n  elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.22@sha256:a0e01a200a316bac8d661e1acae368fd40c9c618847205a12f443c09c36e6eb3\n    environment:\n      - discovery.type=single-node\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - xpack.security.enabled=false  # Disable security features\n      - xpack.security.http.ssl.enabled=false  # Disable HTTPS\n      - xpack.monitoring.enabled=false  # Disable monitoring features\n    ports:\n      - \"9200:9200\""
  },
  {
    "path": "docker-compose/elasticsearch/v8/docker-compose.yml",
    "content": "services:\n  elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:8.19.0@sha256:e1e66bfabae0fd03a0a36651a9bb198e7f061e0c99f457a6203b116e053e9cdb\n    environment:\n      - discovery.type=single-node\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - xpack.security.enabled=false  # Disable security features\n      - xpack.security.http.ssl.enabled=false  # Disable HTTPS\n      - action.destructive_requires_name=false\n      - xpack.monitoring.collection.enabled=false  # Disable monitoring features\n    ports:\n      - \"9200:9200\"\n      \n"
  },
  {
    "path": "docker-compose/elasticsearch/v9/docker-compose.yml",
    "content": "services:\n  elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:9.3.0@sha256:4f6bdcb742e892539c6ac49b0dd3e4e182e90218546e8c6a22db378c344acb60\n    environment:\n      - discovery.type=single-node\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - xpack.security.enabled=false  # Disable security features\n      - xpack.security.http.ssl.enabled=false  # Disable HTTPS\n      - action.destructive_requires_name=false\n      - xpack.monitoring.collection.enabled=false  # Disable monitoring features\n    ports:\n      - \"9200:9200\"\n      \n"
  },
  {
    "path": "docker-compose/kafka/README.md",
    "content": "# Sample configuration with Kafka\n\nThis `docker compose` environment provides a sample configuration of Jaeger deployment utilizing collector-Kafka-ingester pipeline with jaeger-v2 unified binary. Storage is provided by the `jaeger-remote-storage` service running memstore.\n\nThe setup uses **Apache Kafka 3.9.0** running in KRaft mode.\n\nJaeger UI can be accessed at http://localhost:16686/, as usual, and refreshing the screen should produce internal traces.\n\n```mermaid\ngraph LR\n    C[jaeger v2<br>collector mode] --> KafkaBroker\n    KafkaBroker --> I[jaeger v2<br>ingester mode]\n    I --> S[jaeger-remote-storage]\n    UI[jaeger v2<br>query mode<br>Jaeger UI] --> S\n    S --> MemStore\n    subgraph Kafka KRaft\n        KafkaBroker\n    end\n    subgraph Shared Storage\n        S\n        MemStore\n    end\n```\n"
  },
  {
    "path": "docker-compose/kafka/docker-compose.yml",
    "content": "include:\n  - path: v3/docker-compose.yml\n\nservices:\n  jaeger-remote-storage:\n    image: cr.jaegertracing.io/jaegertracing/jaeger-remote-storage@sha256:e78c6093ac38f7cdccf0877750bb21f2cbcc08c2fd3e578966b5796a31f26643\n    ports:\n      - 17271:17271\n    environment:\n      - SPAN_STORAGE_TYPE=memory\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget --no-verbose --tries=1 --spider http://localhost:17270/ || exit 1\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n\n  jaeger-collector:\n    image: cr.jaegertracing.io/jaegertracing/jaeger:2.15.1@sha256:a7dd965687d45507072676db81e6903706ba334dfc92f0c248125cfd9a70c483\n    volumes:\n      - ../../cmd/jaeger/config-kafka-collector.yaml:/etc/jaeger/config.yaml\n    command:\n      - \"--config=/etc/jaeger/config.yaml\"\n    environment:\n      - KAFKA_BROKER=kafka:9092\n      - KAFKA_TOPIC=jaeger-spans\n      - KAFKA_ENCODING=otlp_proto\n    ports:\n      - 4318:4318\n      - 14250:14250\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget --no-verbose --tries=1 --spider http://localhost:13133/status || exit 1\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n    depends_on:\n      kafka:\n        condition: service_healthy\n    links:\n      - kafka\n\n  jaeger-ingester:\n    image: cr.jaegertracing.io/jaegertracing/jaeger:2.15.1@sha256:a7dd965687d45507072676db81e6903706ba334dfc92f0c248125cfd9a70c483\n    volumes:\n      - ./jaeger-ingester-remote-storage.yaml:/etc/jaeger/config.yaml\n    command:\n      - \"--config=/etc/jaeger/config.yaml\"\n    environment:\n      - KAFKA_BROKER=kafka:9092\n      - KAFKA_TOPIC=jaeger-spans\n      - KAFKA_ENCODING=otlp_proto\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget --no-verbose --tries=1 --spider http://localhost:14133/status || exit 1\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n    depends_on:\n      kafka:\n        condition: service_healthy\n      jaeger-remote-storage:\n        condition: service_healthy\n      jaeger-collector:\n        condition: service_healthy\n    links:\n      - kafka\n      - jaeger-remote-storage\n\n  jaeger-query:\n    image: cr.jaegertracing.io/jaegertracing/jaeger:2.15.1@sha256:a7dd965687d45507072676db81e6903706ba334dfc92f0c248125cfd9a70c483\n    volumes:\n      - ../../cmd/jaeger/config-query.yaml:/etc/jaeger/config.yaml\n      - ../../cmd/jaeger/config-ui.json:/cmd/jaeger/config-ui.json:ro\n    command:\n      - \"--config=/etc/jaeger/config.yaml\"\n    environment:\n      - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger-collector:4318\n    ports:\n      - \"16686:16686\"\n      - \"16687\"\n    restart: on-failure\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget --no-verbose --tries=1 --spider http://localhost:13133/status || exit 1\"]\n      interval: 5s\n      timeout: 5s\n      retries: 3\n    depends_on:\n      jaeger-remote-storage:\n        condition: service_healthy\n    links:\n      - jaeger-remote-storage"
  },
  {
    "path": "docker-compose/kafka/jaeger-ingester-remote-storage.yaml",
    "content": "# This config is needed because config-kafka-ingester.yaml uses memory storage,\n# but this docker-compose setup uses jaeger-remote-storage (grpc).\n# Based on config-kafka-ingester.yaml with grpc storage backend.\n\nservice:\n  extensions: [jaeger_storage, healthcheckv2]\n  pipelines:\n    traces:\n      receivers: [kafka]\n      processors: [batch]\n      exporters: [jaeger_storage_exporter]\n  telemetry:\n    resource:\n      service.name: jaeger_ingester\n    logs:\n      level: info\n\nextensions:\n  healthcheckv2:\n    use_v2: true\n    http:\n      endpoint: 0.0.0.0:14133\n\n  jaeger_storage:\n    backends:\n      some_storage:\n        grpc:\n          endpoint: jaeger-remote-storage:17271\n          tls:\n            insecure: true\n\nreceivers:\n  kafka:\n    brokers:\n      - ${env:KAFKA_BROKER:-kafka:9092}\n    traces:\n      topics:\n        - ${env:KAFKA_TOPIC:-jaeger-spans}\n      encoding: ${env:KAFKA_ENCODING:-otlp_proto}\n    initial_offset: earliest\n\nprocessors:\n  batch:\n\nexporters:\n  jaeger_storage_exporter:\n    trace_storage: some_storage\n"
  },
  {
    "path": "docker-compose/kafka/v3/docker-compose.yml",
    "content": "services:\n  kafka:\n    image: apache/kafka:3.9.0@sha256:fbc7d7c428e3755cf36518d4976596002477e4c052d1f80b5b9eafd06d0fff2f\n    hostname: kafka\n    ports:\n      - \"9092:9092\"\n    volumes:\n      - kafka-data:/var/lib/kafka/data\n    environment:\n      # KRaft settings (no ZooKeeper needed)\n      KAFKA_NODE_ID: 1\n      KAFKA_PROCESS_ROLES: broker,controller\n      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9094\n\n      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9094\n      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092\n      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT\n      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER\n      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT\n      # Additional settings\n      KAFKA_AUTO_CREATE_TOPICS_ENABLE: \"true\"\n      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1\n      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1\n      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1\n      # CLUSTER_ID for KRaft\n      CLUSTER_ID: test_kafka_cluster_id\n    healthcheck:\n      test:\n        [\n          \"CMD-SHELL\",\n          \"/opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092\",\n        ]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 30s\n\nvolumes:\n  kafka-data:\n"
  },
  {
    "path": "docker-compose/monitor/.gitignore",
    "content": ".venv\n"
  },
  {
    "path": "docker-compose/monitor/Makefile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nBINARY ?= jaeger # Default value uses v2 binary\n\n.PHONY: build\nbuild: clean-jaeger\n\tcd ../../ && make build-$(BINARY) GOOS=linux \n\tcd ../../ && make create-baseimg PLATFORMS=linux/$(shell go env GOARCH)\n\tcd ../../ && docker buildx build --target release \\\n\t\t--tag jaegertracing/$(BINARY):dev \\\n\t\t--build-arg base_image=localhost:5000/baseimg_alpine:latest \\\n\t\t--build-arg debug_image=not-used \\\n\t\t--build-arg TARGETARCH=$(shell go env GOARCH) \\\n\t\t--load \\\n\t\tcmd/$(BINARY)\n\n# starts up the system required for SPM using the latest otel image and a development jaeger image.\n# Note: the jaeger \"dev\" image can be built with \"make build\".\n.PHONY: dev\ndev: export JAEGER_VERSION = dev\ndev: \n\tdocker compose up $(DOCKER_COMPOSE_ARGS)\n\n.PHONY: elasticsearch\nelasticsearch: export JAEGER_VERSION = dev\nelasticsearch:\n\tdocker compose -f docker-compose-elasticsearch.yml up $(DOCKER_COMPOSE_ARGS)\n\n.PHONY: opensearch\nopensearch: export JAEGER_VERSION = dev\nopensearch:\n\tdocker compose -f docker-compose-opensearch.yml up $(DOCKER_COMPOSE_ARGS)\n\n.PHONY: clean-jaeger\nclean-jaeger:\n\t# Also cleans up intermediate cached containers.\n\tdocker system prune -f\n\n.PHONY: clean-all\nclean-all: clean-jaeger\n\tdocker rmi -f jaegertracing/jaeger:dev ; \\\n\tdocker rmi -f jaegertracing/jaeger:latest ; \\\n\tdocker rmi -f otel/opentelemetry-collector-contrib:latest ; \\\n\tdocker rmi -f prom/prometheus:latest ; \\\n"
  },
  {
    "path": "docker-compose/monitor/README.md",
    "content": "# Service Performance Monitoring (SPM) Development/Demo Environment\n\nService Performance Monitoring (SPM) is an opt-in feature introduced to Jaeger that provides Request, Error and Duration\n(RED) metrics grouped by service name and operation that are derived from span data. These metrics are programmatically\navailable through an API exposed by jaeger-query along with a \"Monitor\" UI tab that visualizes these metrics as graphs.\n\n1.  **Prometheus as metrics backend**: Metrics are computed by the OpenTelemetry Collector and aggregated in Prometheus (see [tracking issue](https://github.com/jaegertracing/jaeger/issues/2954)).\n2.  **Directly querying from trace storage (Elasticsearch/OpenSearch)**: Metrics are computed directly from trace data stored in Elasticsearch or OpenSearch, eliminating the need for a separate metrics storage backend like Prometheus (see [issue \\#6641](https://github.com/jaegertracing/jaeger/issues/6641) for details).\n\nThe motivation for providing this environment is to allow developers to either test Jaeger UI or their own applications against jaeger-query's metrics query API, as well as a quick and simple way for users to bring up the entire stack required to visualize RED metrics from simulated traces or from their own application.\n\nThis environment supports the following backend components, depending on the chosen metrics storage:\n\n- **Common Components**:\n\n    - [MicroSim](https://github.com/yurishkuro/microsim): A program to simulate traces.\n    - [Jaeger](https://www.jaegertracing.io/docs/latest/getting-started/): The full Jaeger stack in a single container image.\n    - [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/): A vendor-agnostic integration layer for traces and metrics. For the Prometheus option, it receives Jaeger spans, forwards them to Jaeger, and aggregates metrics from span data (see [spanmetrics connector documentation][spanmetricsconnectorreadme]).\n\n- **For Prometheus as metrics backend**:\n\n    - [Prometheus](https://prometheus.io/): A metrics collection and query engine that scrapes metrics computed by the OpenTelemetry Collector and provides an API for Jaeger All-in-one to query these metrics.\n\n- **For directly querying from trace storage (ES/OS)**:\n\n    - [Elasticsearch/OpenSearch](https://www.elastic.co/elasticsearch/): The trace storage backend for Jaeger, used for both trace storage and direct metrics querying in this configuration.\n\nThe following diagram illustrates the relationship between these components when using Prometheus as the metrics backend:\n\n```mermaid\nflowchart LR\n    SDK -->|traces| Receiver\n    Receiver --> MG\n    Receiver --> Batch\n    MG --> ExpMetrics\n    Batch --> ExpTraces\n    ExpMetrics -->|metrics| Prometheus[(Prometheus)]\n    ExpTraces -->|traces| Jaeger[Jaeger]\n    Prometheus -.-> JaegerUI\n    Jaeger --> Storage[(Storage)]\n    Storage -.-> JaegerUI[Jaeger\n     UI]\n\n    style Prometheus fill:red,color:white\n    style Jaeger fill:blue,color:white\n    style JaegerUI fill:blue,color:white\n    style Storage fill:gray,color:white\n\n    subgraph Application\n        SDK[OTel\n         SDK]\n    end\n\n    subgraph OTEL[OTel Collector]\n        Receiver\n        Batch\n        MG[Span\n         Metrics\n         Connector]\n        ExpTraces[Traces\n         Exporter]\n        ExpMetrics[Metrics\n         Exporter]\n    end\n```\n\n# Getting Started\n\n## Quickstart\n\nThis brings up the system necessary to use the SPM feature locally.\nIt uses the latest image tags from both Jaeger and OpenTelemetry.\n\n### Option 1: Prometheus as metrics backend\n\n```shell\ndocker compose up\n```\n\n### Option 2: Directly querying from trace storage (ES/OS)\n\n```shell\ndocker compose -f docker-compose-elasticsearch.yml up\n```\n\n```shell\ndocker compose -f docker-compose-opensearch.yml up\n```\n\n\n\n**Tips:**\n- Let the application run for a couple of minutes to ensure there is enough time series data to plot in the dashboard.\n- Navigate to Jaeger UI at http://localhost:16686/ and inspect the Monitor tab. Select `redis` service from the dropdown to see more than one endpoint.\n- For the Prometheus option, visualize raw metrics in the Prometheus UI at http://localhost:9090/query (e.g., [example query for trace spans](http://localhost:9090/query?g0.expr=traces_span_metrics_calls_total&g0.tab=0&g0.range_input=5m)).\n\n**Warning:** The included ` docker compose` files use the `latest` version of Jaeger and other components. If your local Docker registry already contains older versions, which may still be tagged as `latest`, you may want to delete those images before running the full set, to ensure consistent behavior:\n\n```bash\nmake clean-all\n```\n\nTo use an official published image of Jaeger, specify the version via environment variable:\n\n```shell\nJAEGER_VERSION=2.0.0 docker compose -f docker-compose.yml up\n```\n\n## Development\n\nThese steps allow for running the system necessary for SPM, built from Jaeger's source.\n\nThe primary use case is for testing source code changes to the SPM feature locally.\n\n### Build jaeger-v2 docker image\n\n```shell\nmake build\n```\n\n## Bring up the dev environment\n\n```bash\nmake dev\n```\n\n## Sending traces\n\nWe will use [tracegen](https://github.com/jaegertracing/jaeger/tree/main/cmd/tracegen)\nto emit traces to the OpenTelemetry Collector which, in turn, will aggregate the trace data into metrics.\n\nStart the local stack needed for SPM, if not already done:\n\n```shell\ndocker compose up\n```\n\nGenerate a specific number of traces with:\n\n```shell\ndocker run --env OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=\"http://jaeger:4318/v1/traces\" \\\n  --network monitor_backend \\\n  --rm \\\n  jaegertracing/jaeger-tracegen:latest \\\n    -trace-exporter otlp-http \\\n    -traces 1\n```\n\nOr, emit traces over a period of time with:\n\n```shell\ndocker run --env OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=\"http://jaeger:4318/v1/traces\" \\\n  --network monitor_backend \\\n  --rm \\\n  jaegertracing/jaeger-tracegen:latest \\\n    -trace-exporter otlp-http \\\n    -duration 5s\n```\n\nNavigate to Jaeger UI at http://localhost:16686/ and you should be able to see traces from this demo application\nunder the `tracegen` service:\n\n![TraceGen Traces](images/tracegen_traces.png)\n\nThen navigate to the Monitor tab at http://localhost:16686/monitor to view the RED metrics:\n\n![TraceGen RED Metrics](images/tracegen_metrics.png)\n\n## Querying the HTTP API\n\n### Example 1\nFetch call rates for both the driver and frontend services, grouped by operation, from now,\nlooking back 1 second with a sliding rate-calculation window of 1m and step size of 1 millisecond\n\n```bash\ncurl \"http://localhost:16686/api/metrics/calls?service=driver&service=frontend&groupByOperation=true&endTs=$(date +%s)000&lookback=1000&step=100&ratePer=60000\" | jq .\n```\n\n\n### Example 2\nFetch P95 latencies for both the driver and frontend services from now,\nlooking back 1 second with a sliding rate-calculation window of 1m and step size of 1 millisecond, where the span kind is either \"server\" or \"client\".\n\n```bash\ncurl \"http://localhost:16686/api/metrics/latencies?service=driver&service=frontend&quantile=0.95&endTs=$(date +%s)000&lookback=1000&step=100&ratePer=60000&spanKind=server&spanKind=client\" | jq .\n```\n\n### Example 3\n\nFetch error rates for both driver and frontend services using default parameters.\n\n```bash\ncurl \"http://localhost:16686/api/metrics/errors?service=driver&service=frontend\" | jq .\n```\n\n### Example 4\n\nFetch the minimum step size supported by the underlying metrics store.\n\n```bash\ncurl \"http://localhost:16686/api/metrics/minstep\" | jq .\n```\n\n# HTTP API Specification\n\n## Query Metrics\n\n`/api/metrics/{metric_type}?{query}`\n\nWhere (Backus-Naur form):\n\n```\nmetric_type = 'latencies' | 'calls' | 'errors'\n\nquery = services , [ '&' optionalParams ]\n\noptionalParams = param | param '&' optionalParams\n\nparam =  groupByOperation | quantile | endTs | lookback | step | ratePer | spanKinds\n\nservices = service | service '&' services\nservice = 'service=' strValue\n  - The list of services to include in the metrics selection filter, which are logically 'OR'ed.\n  - Mandatory.\n\nquantile = 'quantile=' floatValue\n  - The quantile to compute the latency 'P' value. Valid range (0,1].\n  - Mandatory for 'latencies' type.\n\ngroupByOperation = 'groupByOperation=' boolValue\nboolValue = '1' | 't' | 'T' | 'true' | 'TRUE' | 'True' | 0 | 'f' | 'F' | 'false' | 'FALSE' | 'False'\n  - A boolean value which will determine if the metrics query will also group by operation.\n  - Optional with default: false\n\nendTs = 'endTs=' intValue\n  - The posix milliseconds timestamp of the end time range of the metrics query.\n  - Optional with default: now\n\nlookback = 'lookback=' intValue\n  - The duration, in milliseconds, from endTs to look back on for metrics data points.\n  - For example, if set to `3600000` (1 hour), the query would span from `endTs - 1 hour` to `endTs`.\n  - Optional with default: 3600000 (1 hour).\n\nstep = 'step=' intValue\n  - The duration, in milliseconds, between data points of the query results.\n  - For example, if set to 5s, the results would produce a data point every 5 seconds from the `endTs - lookback` to `endTs`.\n  - Optional with default: 5000 (5 seconds).\n\nratePer = 'ratePer=' intValue\n  - The duration, in milliseconds, in which the per-second rate of change is calculated for a cumulative counter metric.\n  - Optional with default: 600000 (10 minutes).\n\nspanKinds = spanKind | spanKind '&' spanKinds\nspanKind = 'spanKind=' spanKindType\nspanKindType = 'unspecified' | 'internal' | 'server' | 'client' | 'producer' | 'consumer'\n  - The list of spanKinds to include in the metrics selection filter, which are logically 'OR'ed.\n  - Optional with default: 'server'\n```\n\n\n## Min Step\n\n`/api/metrics/minstep`\n\nGets the min time resolution supported by the backing metrics store, in milliseconds, that can be used in the `step` parameter.\ne.g. a min step of 1 means the backend can only return data points that are at least 1ms apart, not closer.\n\n## Responses\n\nThe response data model is based on [`MetricsFamily`](https://github.com/jaegertracing/jaeger/blob/main/internal/proto/metrics/openmetrics.proto#L53).\n\nFor example:\n\n```\n{\n  \"name\": \"service_call_rate\",\n  \"type\": \"GAUGE\",\n  \"help\": \"calls/sec, grouped by service\",\n  \"metrics\": [\n    {\n      \"labels\": [\n        {\n          \"name\": \"service_name\",\n          \"value\": \"driver\"\n        }\n      ],\n      \"metricPoints\": [\n        {\n          \"gaugeValue\": {\n            \"doubleValue\": 0.005846808321083344\n          },\n          \"timestamp\": \"2021-06-03T09:12:06Z\"\n        },\n        {\n          \"gaugeValue\": {\n            \"doubleValue\": 0.006960443672323934\n          },\n          \"timestamp\": \"2021-06-03T09:12:11Z\"\n        },\n      ]\n...\n    }\n...\n  ]\n...\n}\n  ```\n\nIf the `groupByOperation=true` parameter is set, the response will include the operation name in the labels like so:\n\n```\n      \"labels\": [\n        {\n          \"name\": \"operation\",\n          \"value\": \"/FindNearest\"\n        },\n        {\n          \"name\": \"service_name\",\n          \"value\": \"driver\"\n        }\n      ],\n```\n\n# Disabling Metrics Querying\n\nAs this is feature is opt-in only, disabling metrics querying simply involves omitting the `METRICS_STORAGE_TYPE` environment variable when starting-up jaeger-query or jaeger all-in-one.\n\nFor example, try removing the `METRICS_STORAGE_TYPE=prometheus` environment variable from the [docker-compose.yml](./docker-compose.yml) file.\n\nThen querying any metrics endpoints results in an error message:\n\n```\n$ curl http://localhost:16686/api/metrics/minstep | jq .\n{\n  \"data\": null,\n  \"total\": 0,\n  \"limit\": 0,\n  \"offset\": 0,\n  \"errors\": [\n    {\n      \"code\": 405,\n      \"msg\": \"metrics querying is currently disabled\"\n    }\n  ]\n}\n```\n\n[spanmetricsconnector]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/spanmetricsconnector\n[spanmetricsconnectorreadme]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/connector/spanmetricsconnector/README.md\n"
  },
  {
    "path": "docker-compose/monitor/datasource.yml",
    "content": "apiVersion: 1\ndatasources:\n- name: Prometheus\n  type: prometheus\n  url: http://prometheus:9090\n  isDefault: true\n  access: proxy\n  editable: true\n"
  },
  {
    "path": "docker-compose/monitor/docker-compose-elasticsearch.yml",
    "content": "services:\n  elasticsearch:\n    image: docker.elastic.co/elasticsearch/elasticsearch:9.3.0@sha256:4f6bdcb742e892539c6ac49b0dd3e4e182e90218546e8c6a22db378c344acb60\n    networks:\n      - backend\n    environment:\n      - discovery.type=single-node\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - xpack.security.enabled=false  # Disable security features\n      - xpack.security.http.ssl.enabled=false  # Disable HTTPS\n      - action.destructive_requires_name=false\n      - xpack.monitoring.collection.enabled=false  # Disable monitoring features\n      - ES_LOG_LEVEL=trace  # Set log level to capture all queries\n      - logger.org.elasticsearch.index.search.slowlog=TRACE  # Log all slow queries\n\n    ports:\n      - \"9200:9200\"\n    healthcheck:\n      test: [ \"CMD-SHELL\", \"curl -f http://localhost:9200 || exit 1\" ]\n      interval: 10s\n      timeout: 10s\n      retries: 30\n\n  jaeger:\n    networks:\n      backend:\n        # This is the host name used in Prometheus scrape configuration.\n        aliases: [ spm_metrics_source ]\n    image: jaegertracing/jaeger:${JAEGER_VERSION:-latest}\n    volumes:\n      - \"./jaeger-ui.json:/etc/jaeger/jaeger-ui.json\" # Do we need this for v2 ? Seems to be running without this.\n      - \"../../cmd/jaeger/config-spm-elasticsearch.yaml:/etc/jaeger/config.yml\"\n    command: [\"--config\", \"/etc/jaeger/config.yml\"]\n    environment:\n      - SPANMETRICS_FLUSH_INTERVAL=${SPANMETRICS_FLUSH_INTERVAL:-60s}\n    ports:\n      - \"16686:16686\" # Jaeger UI http://localhost:16686\n      - \"8888:8888\"\n      - \"8889:8889\"\n      - \"4317:4317\"\n      - \"4318:4318\"\n    depends_on:\n      elasticsearch:\n        condition: service_healthy\n\n  microsim:\n    networks:\n      - backend\n    image: yurishkuro/microsim:v0.6.0@sha256:fd75a9b3dd1bb4d7d305a562edeac60051a7fec784b898ff7ab834eacc73f41e\n    command: \"-d 24h -s 500ms\"\n    environment:\n      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://jaeger:4318/v1/traces\n    depends_on:\n      - jaeger\n\nnetworks:\n  backend:\n\nvolumes:\n  esdata:\n    driver: local\n"
  },
  {
    "path": "docker-compose/monitor/docker-compose-opensearch.yml",
    "content": "services:\n  opensearch:\n    image: opensearchproject/opensearch:3.5.0@sha256:919ff4e7d0d57dbc4bd0999ddf0e43e961bba844ec2a5b6734fc979eb4e32399\n    networks:\n      - backend\n    environment:\n      - discovery.type=single-node\n      - plugins.security.disabled=true\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=passRT%^#234\n    ports:\n      - \"9200:9200\"\n    healthcheck:\n      test: [ \"CMD-SHELL\", \"curl -f http://localhost:9200 || exit 1\" ]\n      interval: 10s\n      timeout: 10s\n      retries: 30\n\n  jaeger:\n    networks:\n      backend:\n        # This is the host name used in Prometheus scrape configuration.\n        aliases: [ spm_metrics_source ]\n    image: jaegertracing/jaeger:${JAEGER_VERSION:-latest}\n    volumes:\n      - \"./jaeger-ui.json:/etc/jaeger/jaeger-ui.json\"\n      - \"../../cmd/jaeger/config-spm-opensearch.yaml:/etc/jaeger/config.yml\"\n    command: [\"--config\", \"/etc/jaeger/config.yml\"]\n    environment:\n      - SPANMETRICS_FLUSH_INTERVAL=${SPANMETRICS_FLUSH_INTERVAL:-60s}\n    ports:\n      - \"16686:16686\" # Jaeger UI http://localhost:16686\n      - \"8888:8888\"\n      - \"8889:8889\"\n      - \"4317:4317\"\n      - \"4318:4318\"\n    depends_on:\n      opensearch:\n        condition: service_healthy\n\n  microsim:\n    networks:\n      - backend\n    image: yurishkuro/microsim:v0.6.0@sha256:fd75a9b3dd1bb4d7d305a562edeac60051a7fec784b898ff7ab834eacc73f41e\n    command: \"-d 24h -s 500ms\"\n    environment:\n      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://jaeger:4318/v1/traces\n    depends_on:\n      - jaeger\n\nnetworks:\n  backend:\n\nvolumes:\n  esdata:\n    driver: local\n"
  },
  {
    "path": "docker-compose/monitor/docker-compose.yml",
    "content": "services:\n  jaeger:\n    networks:\n      backend:\n        # This is the host name used in Prometheus scrape configuration.\n        aliases: [ spm_metrics_source ]\n    image: jaegertracing/jaeger:${JAEGER_VERSION:-latest}\n    volumes:\n      - \"./jaeger-ui.json:/etc/jaeger/jaeger-ui.json\" # Do we need this for v2 ? Seems to be running without this.\n      - \"../../cmd/jaeger/config-spm.yaml:/etc/jaeger/config.yml\"\n    command: [\"--config\", \"/etc/jaeger/config.yml\"]\n    environment:\n      - SPANMETRICS_FLUSH_INTERVAL=${SPANMETRICS_FLUSH_INTERVAL:-60s}\n    ports:\n      - \"16686:16686\"\n      - \"16687:16687\"\n      - \"8888:8888\"\n      - \"8889:8889\"\n      - \"4317:4317\"\n      - \"4318:4318\"\n\n  microsim:\n    networks:\n      - backend\n    image: yurishkuro/microsim:v0.6.0@sha256:fd75a9b3dd1bb4d7d305a562edeac60051a7fec784b898ff7ab834eacc73f41e\n    command: \"-d 24h -s 500ms\"\n    environment:\n      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://jaeger:4318/v1/traces\n    depends_on:\n      - jaeger\n\n  prometheus:\n    networks:\n      - backend\n    image: prom/prometheus:v3.10.0@sha256:4a61322ac1103a0e3aea2a61ef1718422a48fa046441f299d71e660a3bc71ae9\n    volumes:\n      - \"./prometheus.yml:/etc/prometheus/prometheus.yml\"\n    ports:\n      - \"9090:9090\"\n\nnetworks:\n  backend:\n"
  },
  {
    "path": "docker-compose/monitor/jaeger-ui.json",
    "content": "{\n  \"monitor\": {\n    \"menuEnabled\": true\n  },\n  \"dependencies\": {\n    \"menuEnabled\": true\n  }\n}\n"
  },
  {
    "path": "docker-compose/monitor/otel-collector-config-connector.yml",
    "content": "receivers:\n  otlp:\n    protocols:\n      grpc:\n      http:\n        endpoint: \"0.0.0.0:4318\"\n\nexporters:\n  prometheus:\n    endpoint: \"0.0.0.0:8889\"\n\n  otlp:\n    endpoint: jaeger:4317\n    tls:\n      insecure: true\n\nconnectors:\n  spanmetrics:\n\nprocessors:\n  batch:\n\nservice:\n  pipelines:\n    traces:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [spanmetrics, otlp]\n\n    # The metrics pipeline receives generated span metrics from 'spanmetrics' connector\n    # and pushes to Prometheus exporter, which makes them available for scraping on :8889.\n    metrics/spanmetrics:\n      receivers: [spanmetrics]\n      exporters: [prometheus]\n"
  },
  {
    "path": "docker-compose/monitor/prometheus.yml",
    "content": "global:\n  \n  # Set low scrape and evaluation intervals to speed up e2e test \n  scrape_interval:     5s # Default is every 15sec\n  evaluation_interval: 5s # Default is every 15sec\n\nscrape_configs:\n  - job_name: aggregated-trace-metrics\n    static_configs:\n    - targets: ['spm_metrics_source:8889']\n  - job_name: jaeger-collector-metrics\n    static_configs:\n    - targets: [ 'spm_metrics_source:8888' ]"
  },
  {
    "path": "docker-compose/opensearch/v1/docker-compose.yml",
    "content": "services:\n  opensearch:\n    image: opensearchproject/opensearch:1.3.17@sha256:749c568af3106a12b1600673ab8dd2d1980d1c699a09f10cbcf6a003d8e38aed\n    environment:\n      - discovery.type=single-node\n      - plugins.security.disabled=true\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n    ports:\n      - \"9200:9200\"  \n"
  },
  {
    "path": "docker-compose/opensearch/v2/docker-compose.yml",
    "content": "services:\n  opensearch:\n    image: opensearchproject/opensearch:2.19.0@sha256:1f8b88245a6af61e7aa500afe0e87d43401e4b33140bb47230a919428ce3f7cb\n    environment:\n      - discovery.type=single-node\n      - plugins.security.disabled=true\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=passRT%^#234\n    ports:\n      - \"9200:9200\"\n"
  },
  {
    "path": "docker-compose/opensearch/v3/docker-compose.yml",
    "content": "services:\n  opensearch:\n    image: opensearchproject/opensearch:3.5.0@sha256:2f2244c7c0ad3a4a0d09977c2b519977b77bacc92e1eaf995c080c1d22f517b6\n    environment:\n      - discovery.type=single-node\n      - plugins.security.disabled=true\n      - http.host=0.0.0.0\n      - transport.host=127.0.0.1\n      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=passRT%^#234\n    ports:\n      - \"9200:9200\"\n"
  },
  {
    "path": "docker-compose/scylladb/README.md",
    "content": "## ScyllaDB as a storage backend\nJaeger could be configured to use ScyllaDB as a storage backend. This is an experimental feature and this is not an officially supported backend, meaning that Jaeger team will not proactively address any issues that may arise from incompatibilities between the ScyllaDB and Cassandra databases (the team may still accept PRs).\n\n### Configuration\n\nSetup Jaeger server to use Cassandra database and just replace conn string to ScyllaDB cluster. No additional configuration is required.\n\n### Known issues\n\n#### Protocol version\n\nJaeger server detects Cassandra protocol version automatically. At the date of the demo with specified versions server detects that it connected via protocol version 3 while it is actually 4. This leads to warn log in cassandra-schema container:\n```\nWARN: DESCRIBE|DESC was moved to server side in Cassandra 4.0. As a consequence DESRIBE|DESC will not work in cqlsh '6.0.0' connected to Cassandra '3.0.8', the version that you are connected to. DESCRIBE does not exist server side prior Cassandra 4.0.\nCassandra version detected: 3\n```\n\nOtherwise, it should be fully compatible.\n\n### Demo\n\nDocker compose file consists of Jaeger server, Jaeger Cassandra schema writer, Jaeger UI, Jaeger Demo App `HotRod` and a ScyllaDB cluster.\n\nThere is a known issue with docker compose network configuration and containers connectivity on Apple silicone. Sometimes it's helpful to manually create the docker network before running `docker compose up`:\n```shell\ndocker network create --driver bridge jaeger-scylladb\n```\n\n#### Spin up all infrastructure:\n\n```shell\ndocker compose up -d\n```\nWill:\n1. Create ScyllaDB cluster with 3 nodes(about 1 min to initialize)\n2. Generate the schema for jaeger key space\n3. Start Jaeger server, Jaeger UI and Jaeger Demo App `HotRod`\n\n#### Run demo app\n\n1. Wait till all containers are up and running\n2. Open Demo app in your browser: http://localhost:8080 and click some buttons.\n3. Open Jaeger UI in your browser: http://localhost:16686 and check traces\n"
  },
  {
    "path": "docker-compose/scylladb/docker-compose.yml",
    "content": "# docker compose file to test Scylla with Jaeger.\n\n# Disclaimer: This defaults to using 'latest' image tag for Jaeger images,\n# which can be stale in your local repository. In case of issues try running\n# against the actual Jaeger version like JAEGER_VERSION=1.59.0.\n\nnetworks:\n  jaeger-scylladb:\n\nservices:\n  jaeger:\n    restart: unless-stopped\n    image: cr.jaegertracing.io/jaegertracing/jaeger:${JAEGER_VERSION:-latest}\n    volumes:\n      - ../../cmd/jaeger/config-cassandra.yaml:/etc/jaeger/config.yaml\n    command:\n      - \"--config=/etc/jaeger/config.yaml\"\n    environment:\n      - CASSANDRA_CONTACT_POINTS=scylladb:9042\n    ports:\n      - 16686:16686\n      - 16687:16687\n      - 4317:4317\n      - 4318:4318\n    networks:\n      - jaeger-scylladb\n    depends_on:\n      - cassandra-schema\n\n  cassandra-schema:\n    image: cr.jaegertracing.io/jaegertracing/jaeger-cassandra-schema:${JAEGER_VERSION:-latest}\n    environment:\n      CASSANDRA_PROTOCOL_VERSION: 4\n      CASSANDRA_VERSION: 4\n      CQLSH_HOST: scylladb\n      DATACENTER: test\n      MODE: test\n    networks:\n      - jaeger-scylladb\n    depends_on:\n      scylladb:\n        condition: service_healthy\n\n  scylladb:\n    restart: always\n    image: scylladb/scylla:5.4.7@sha256:d73f652cbce3622827eeff35a650936d70b2bf2939ea5dd6b7e6c3e8944537fe\n    ports:\n      - 9042:9042\n    volumes:\n      - .docker/scylladb/1:/var/lib/scylla\n    networks:\n      - jaeger-scylladb\n    healthcheck:\n      test: [\"CMD\", \"cqlsh\", \"-e\", \"describe keyspaces\"]\n      interval: 1s\n      retries: 120\n      timeout: 1s\n\n  scylladb2:\n    restart: always\n    image: scylladb/scylla:5.4.7@sha256:d73f652cbce3622827eeff35a650936d70b2bf2939ea5dd6b7e6c3e8944537fe\n    command: --seeds=scylladb\n    volumes:\n      - .docker/scylladb/2:/var/lib/scylla\n    networks:\n      - jaeger-scylladb\n\n  scylladb3:\n    restart: always\n    image: scylladb/scylla:5.4.7@sha256:d73f652cbce3622827eeff35a650936d70b2bf2939ea5dd6b7e6c3e8944537fe\n    command: --seeds=scylladb\n    volumes:\n      - .docker/scylladb/3:/var/lib/scylla\n    networks:\n      - jaeger-scylladb\n\n  hotrod:\n    image: cr.jaegertracing.io/jaegertracing/example-hotrod:${JAEGER_VERSION:-latest}\n    container_name: hotrod\n    ports:\n      - 8080:8080\n    command: [ \"all\" ]\n    environment:\n      - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318\n    networks:\n      - jaeger-scylladb\n    depends_on:\n      - jaeger\n"
  },
  {
    "path": "docker-compose/tail-sampling/Makefile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nBINARY ?= jaeger\n\n.PHONY: build\nbuild: clean-jaeger\n\tcd ../../ && make build-$(BINARY) GOOS=linux \n\tcd ../../ && make create-baseimg PLATFORMS=linux/$(shell go env GOARCH)\n\tcd ../../ && docker buildx build --target release \\\n\t\t--tag jaegertracing/$(BINARY):dev \\\n\t\t--build-arg base_image=localhost:5000/baseimg_alpine:latest \\\n\t\t--build-arg debug_image=not-used \\\n\t\t--build-arg TARGETARCH=$(shell go env GOARCH) \\\n\t\t--load \\\n\t\tcmd/$(BINARY)\n\n.PHONY: dev\ndev: export JAEGER_VERSION = dev\ndev: build \n\tdocker compose -f docker-compose.yml up $(DOCKER_COMPOSE_ARGS)\n\n.PHONY: clean-jaeger\nclean-jaeger:\n\t# Also cleans up intermediate cached containers.\n\tdocker system prune -f\n\n.PHONY: clean-all\nclean-all: clean-jaeger\n\tdocker rmi -f otel/opentelemetry-collector-contrib:latest\n"
  },
  {
    "path": "docker-compose/tail-sampling/README.md",
    "content": "# Tail-Based Sampling Processor\n\nThis `docker compose` environment provides a sample configuration of a Jaeger collector utilizing the\n[Tail-Based Sampling Processor in OpenTelemtry](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/tailsamplingprocessor/README.md).\n\n## Description of Setup\n\nThe `docker-compose.yml` contains three services and their functions are outlined as follows:\n\n1. `jaeger` - This is the Jaeger V2 collector that samples traces using the `tail_sampling` processor.\nThe configuration for this service is in [jaeger-v2-config.yml](./jaeger-v2-config.yml).\nThe `tail_sampling` processor has one policy that only captures traces from the services `tracegen-02` and `tracegen-04`.\nFor a full list of policies that can be added to the `tail_sampling` processor, check out [this README](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/tailsamplingprocessor/README.md).\n2. `otel_collector` - This is an OpenTelemtry collector with a `loadbalancing` exporter that routes requests to `jaeger`.\nThe configuration for this service is in [otel-collector-config-connector.yml](./otel-collector-config-connector.yml).\nThe purpose of this collector is to collect spans from different services and forward all spans with the same `traceID`\nto the same downstream collector instance (`jaeger` in this case), so that sampling decisions for a given trace can be\nmade in the same collector instance.\n3. `tracegen` - This is a service that generates traces for 5 different services and sends them to `otel_collector`\n(which will in turn send them to `jaeger`).\n\nNote that in this minimal setup, a `loadbalancer` collector is not necessary since we are only running a\nsingle instance of the `jaeger` collector. In a real-world distributed system running multiple instances\nof the `jaeger` collector, a load balancer is necessary to avoid spans from the same trace being routed\nto different collector instances.\n\n## Running the Example\n\nThe example can be run using the following command:\n\n```bash\nmake dev\n```\n\nTo see the tail-based sampling processor in action, go to the Jaeger UI at <http://localhost:16686/>. \nYou will see that only traces for the services outlined in the policy in [jaeger-v2-config.yml](./jaeger-v2-config.yml) \nare sampled.\n"
  },
  {
    "path": "docker-compose/tail-sampling/docker-compose.yml",
    "content": "services:\n  jaeger:\n    networks:\n      backend:\n    image: cr.jaegertracing.io/jaegertracing/jaeger:${JAEGER_VERSION:-latest}\n    volumes:\n      - \"./jaeger-v2-config.yml:/etc/jaeger/config.yml\"\n    command: [\"--config\", \"/etc/jaeger/config.yml\"]\n    ports:\n      - \"16686:16686\"\n      - \"4317\"\n\n  otel_collector:\n    networks:\n      backend:\n    image: otel/opentelemetry-collector-contrib:${OTEL_IMAGE_TAG:-0.108.0}\n    volumes:\n      - ${OTEL_CONFIG_SRC:-./otel-collector-config-connector.yml}:/etc/otelcol/otel-collector-config.yml\n    command: --config /etc/otelcol/otel-collector-config.yml\n    depends_on:\n      - jaeger\n    ports:\n      - \"4318\"\n\n  tracegen:\n    networks:\n      - backend\n    image: cr.jaegertracing.io/jaegertracing/jaeger-tracegen:2.15.1@sha256:8149733c9c54c2b272d2141388aa4f0ae95704c814df976b434fba162752a235\n    environment:\n      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel_collector:4318/v1/traces\n    command: [\"-workers\", \"3\", \"-pause\", \"250ms\", \"-services\", \"5\", \"-duration\", \"10s\"]\n    depends_on:\n      - jaeger\n\nnetworks:\n  backend:"
  },
  {
    "path": "docker-compose/tail-sampling/jaeger-v2-config.yml",
    "content": "service:\n  extensions: [jaeger_storage, jaeger_query, healthcheckv2]\n  pipelines:\n    traces:\n      receivers: [otlp]\n      processors: [tail_sampling]\n      exporters: [jaeger_storage_exporter]\n  telemetry:\n    logs:\n      level: DEBUG\n\nextensions:\n  healthcheckv2:\n    use_v2: true\n    http:\n  jaeger_query:\n    storage:\n      traces: some_storage\n  jaeger_storage:\n    backends:\n      some_storage:\n        memory:\n          max_traces: 100000\n\nreceivers:\n  otlp:\n    protocols:\n      grpc:\n        endpoint: 0.0.0.0:4317\n\nprocessors:\n  tail_sampling:\n    decision_wait: 5s\n    policies:\n      [\n        {\n          name: filter-by-attribute,\n          type: string_attribute,\n          string_attribute:\n            { key: service.name, values: [tracegen-02, tracegen-04] },\n        },\n      ]\n\nexporters:\n  jaeger_storage_exporter:\n    trace_storage: some_storage\n"
  },
  {
    "path": "docker-compose/tail-sampling/otel-collector-config-connector.yml",
    "content": "receivers:\n  otlp:\n    protocols:\n      http:\n        endpoint: 0.0.0.0:4318\n\nexporters:\n  loadbalancing:\n    routing_key: \"traceID\"\n    protocol:\n      otlp:\n        timeout: 1s\n        tls:\n          insecure: true\n    resolver:\n      static:\n        hostnames:\n        - jaeger:4317\n\nservice:\n  pipelines:\n    traces:\n      receivers:\n        - otlp\n      processors: []\n      exporters:\n        - loadbalancing"
  },
  {
    "path": "docs/SCARF.md",
    "content": "# Setup for Scarf\n\nThis document outlines our implementation details for [Scarf](https://scarf.sh) which provides usage and download analytics for the Jaeger project. \n\n## DNS Configuration\nThe following CNAMES were setup in Netlify for us to utilize the services:\n\n  * `scarf.jaegertracing.io` -> `static.scarf.sh` (used for tracking pixel on webpages)\n  * `cr.jaegertracing.io` -> `gateway.scarf.sh` (used for container registries)\n  * `download.jaegertracing.io` -> `gateway.scarf.sh` (used for file downloads of Jaeger artifacts)\n\nWe also had to add the following TXT verification records:\n1. _scarf-sh-challenge-jaeger.cr.jaegertracing.io - ZN4RLVE3CENVUIXDBNYCa\n2. _scarf-sh-challenge-jaeger.download.jaegertracing.io - U2GBROI64YGH2JLRTXPI\n3. _scarf-sh-challenge-jaeger.scarf.jaegertracing.io - AKB26262A53WP55R4EXR\n\n## Download and Docker Configuration\nThe following setup has been done on Scarf. Previously what was the download link for example https://github.com/jaegertracing/jaeger/releases/download/v1.69.0/jaeger-2.6.0-darwin-amd64.tar.gz should now be https://download.jaegertracing.io/v1.69.0/jaeger-2.6.0-darwin-amd64.tar.gz for us to get analytics.\n\nFor Docker containers the previous command : docker pull jaegertracing/all-in-one should now be docker pull cr.jaegertracing.io/jaegertracing/all-in-one\n\n# Integrating Scarf Analytics with `www.jaegertracing.io` on Netlify\n\nInjecting the Scarf analytics tracking pixel to the `www.jaegertracing.io` website hosted on Netlify is done using Netlify's **Snippet Injection** feature.\n\n## Steps to Add the Scarf Pixel\n\n1.  **Log in to Netlify:**\n\n      * Head over to [app.netlify.com](https://app.netlify.com/) and sign in to your account which has access to the `www.jaegertracing.io` site configuration.\n\n2.  **Select Your Site:**\n\n      * From the list of sites, click on `www.jaegertracing.io`.\n\n3.  **Go to Project Configuration:**\n\n      * On the `www.jaegertracing.io` site dashboard, click on **\"Site settings\"** (you'll usually find this at the top right).\n\n4.  **Access Build & Deploy Settings:**\n\n      * In the left-hand sidebar, under \"Site settings,\" click on **\"Build & deploy.\"**\n\n5.  **Find Snippet Injection:**\n\n      * Scroll down to the \"Post processing\" section and find the **\"Snippet Injection\"** section.\n      * Click the **\"Add Snippet\"** button.\n\n6.  **Configure the Scarf Snippet:**\n\n      * A form will pop up for your new snippet:\n          * **Snippet Name:** Give it a clear name like `Scarf Tracking Pixel`.\n\n          * **Position:** Choose **\"Before `</body>`\"**. This is generally a good spot for image-based pixels as it doesn't block the initial page render.\n\n          * **Snippet Body:** Paste the following HTML code block into this text area. **The pixel ID `cf7517a5-bfa0-4796-b760-1bb4e302e541` is already included.**\n\n            ```html\n            <img referrerpolicy=\"no-referrer-when-downgrade\" src=\"https://scarf.jaegertracing.io/a.png?x-pxid=cf7517a5-bfa0-4796-b760-1bb4e302e541\" alt=\"\" style=\"position: absolute; width: 0; height: 0; border: 0;\" />\n            ```\n\n7.  **Save the Snippet:**\n\n      * Click the **\"Save\"** button to apply the changes.\n\n-----\n\n## Verification\n\nOnce you save, Netlify will automatically inject this image tag into the HTML pages of `www.jaegertracing.io`. You won't need to trigger a new deployment. Scarf.sh should start receiving analytics data from the website shortly after.\n\nTo verify the pixel is loading correctly:\n\n  * Visit `www.jaegertracing.io` in a browser.\n  * Open your browser's **developer tools** (usually by pressing F12 or right-clicking and selecting \"Inspect\").\n  * Go to the **\"Network\"** tab and filter by \"a.png\". You should see requests being made to `https://scarf.jaegertracing.io/a.png`.\n  "
  },
  {
    "path": "docs/adr/001-cassandra-find-traces-duration.md",
    "content": "# Cassandra FindTraceIDs Duration Query Behavior\n\n* **Status**: Documented existing implementation\n* **Date**: 2026-01-03\n\n## Context\n\nThe Cassandra spanstore implementation in Jaeger handles trace queries with duration filters (DurationMin/DurationMax) through a separate code path that cannot efficiently intersect with other query parameters like tags or general operation name filters. This behavior differs from other storage backends like Badger and may seem counterintuitive to users.\n\n### Data Model and Cassandra Constraints\n\nCassandra's data model imposes specific constraints on query patterns. The `duration_index` table is defined with the following schema structure (as referenced in the CQL insertion query in [`internal/storage/v1/cassandra/spanstore/writer.go`](../../internal/storage/v1/cassandra/spanstore/writer.go)):\n\n```cql\nINSERT INTO duration_index(service_name, operation_name, bucket, duration, start_time, trace_id)\nVALUES (?, ?, ?, ?, ?, ?)\n```\n\nThis schema uses a composite partition key consisting of `service_name`, `operation_name`, and `bucket` (an hourly time bucket), with `duration` as a clustering column. In Cassandra, **partition keys require equality constraints** in WHERE clauses - you cannot perform range queries or arbitrary intersections across different partition keys efficiently.\n\n### Duration Index Structure\n\nThe duration index is bucketed by hour to limit partition size and improve query performance. From [`internal/storage/v1/cassandra/spanstore/writer.go`](../../internal/storage/v1/cassandra/spanstore/writer.go) (line 57):\n\n```go\ndurationBucketSize = time.Hour\n```\n\nWhen a span is indexed, its start time is rounded to the nearest hour bucket (line 231 in writer.go):\n\n```go\ntimeBucket := startTime.Round(durationBucketSize)\n```\n\nThe indexing function in `indexByDuration` (lines 229-243) creates two index entries per span:\n1. One indexed by service name alone (with empty operation name)\n2. One indexed by both service name and operation name\n\n```go\nindexByOperationName(\"\")                 // index by service name alone\nindexByOperationName(span.OperationName) // index by service name and operation name\n```\n\n### Query Path Implementation\n\nIn [`internal/storage/v1/cassandra/spanstore/reader.go`](../../internal/storage/v1/cassandra/spanstore/reader.go), the `findTraceIDs` method (lines 275-301) performs an early return when duration parameters are present:\n\n```go\nfunc (s *SpanReader) findTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {\n\tif traceQuery.DurationMin != 0 || traceQuery.DurationMax != 0 {\n\t\treturn s.queryByDuration(ctx, traceQuery)\n\t}\n\t// ... other query paths\n}\n```\n\nThis early return means that when a duration query is detected, **all other query parameters except ServiceName and OperationName are effectively ignored** (tags, for instance, are not processed).\n\nThe `queryByDuration` method (lines 333-375) iterates over hourly buckets within the query time range and issues a Cassandra query for each bucket:\n\n```go\nstartTimeByHour := traceQuery.StartTimeMin.Round(durationBucketSize)\nendTimeByHour := traceQuery.StartTimeMax.Round(durationBucketSize)\n\nfor timeBucket := endTimeByHour; timeBucket.After(startTimeByHour) || timeBucket.Equal(startTimeByHour); timeBucket = timeBucket.Add(-1 * durationBucketSize) {\n\tquery := s.session.Query(\n\t\tqueryByDuration,\n\t\ttimeBucket,\n\t\ttraceQuery.ServiceName,\n\t\ttraceQuery.OperationName,\n\t\tminDurationMicros,\n\t\tmaxDurationMicros,\n\t\ttraceQuery.NumTraces*limitMultiple)\n\t// execute query...\n}\n```\n\nEach query specifies exact values for `bucket`, `service_name`, and `operation_name` (the partition key components), along with a range filter on `duration` (the clustering column). The query definition (lines 51-55) is:\n\n```cql\nSELECT trace_id\nFROM duration_index\nWHERE bucket = ? AND service_name = ? AND operation_name = ? AND duration > ? AND duration < ?\nLIMIT ?\n```\n\n### Why Not Intersect with Other Indices?\n\nUnlike storage backends such as Badger (which can perform hash-joins and arbitrary index intersections), Cassandra's partition-based architecture makes cross-index intersections expensive and impractical:\n\n1. **Partition key constraints**: The duration index requires equality on `(service_name, operation_name, bucket)`. You cannot efficiently query across multiple operations or join with the tag index without scanning many partitions.\n   \n2. **No server-side joins**: Cassandra does not support server-side joins. To intersect duration results with tag results, the client would need to:\n   - Query the duration index for all matching trace IDs\n   - Query the tag index for all matching trace IDs\n   - Perform a client-side intersection\n   \n   This would be inefficient for large result sets and would require fetching potentially many trace IDs over the network.\n\n3. **Hourly bucket iteration**: The duration query already iterates over hourly buckets. Adding tag intersections would multiply the number of queries and result sets to merge.\n\n### Comparison with Badger\n\nThe Badger storage backend handles duration queries differently. In [`internal/storage/v1/badger/spanstore/reader.go`](../../internal/storage/v1/badger/spanstore/reader.go) (around line 486), the `FindTraceIDs` method performs duration queries and then uses the results as a filter (`hashOuter`) that can be intersected with other index results:\n\n```go\nif query.DurationMax != 0 || query.DurationMin != 0 {\n\tplan.hashOuter = r.durationQueries(plan, query)\n}\n```\n\nBadger uses an embedded key-value store where range scans and in-memory filtering are efficient, allowing it to merge results from multiple indices. This is a fundamental difference from Cassandra's distributed, partition-oriented design.\n\n## Decision\n\n**The Cassandra spanstore will continue to treat duration queries as a separate query path that does not intersect with tag indices or other non-service/operation filters.**\n\nWhen a `TraceQueryParameters` contains `DurationMin` or `DurationMax`:\n- The query will use the `duration_index` table exclusively\n- Only `ServiceName` and `OperationName` parameters will be respected (used as partition key components)\n- Tag filters and other parameters will be ignored\n- The code will iterate over hourly time buckets within the query time range\n\nThis approach is documented in code comments and in this ADR to set proper expectations.\n\n## Consequences\n\n### Positive\n\n1. **Performance**: Duration queries execute efficiently by scanning only relevant Cassandra partitions (scoped to service, operation, and hourly bucket).\n2. **Scalability**: The bucketed partition strategy prevents hot partitions and distributes load across the cluster.\n3. **Simplicity**: The implementation is straightforward and leverages Cassandra's strengths (partition-scoped queries with range filtering on clustering columns).\n\n### Negative\n\n1. **Limited query expressiveness**: Users cannot combine duration filters with tag filters in a single query. They must choose one or the other.\n2. **Expectation mismatch**: Users familiar with other backends (like Badger) may expect duration and tags to be combinable.\n3. **Workarounds required**: Applications that need both duration and tag filtering must:\n   - Issue separate queries (one with duration, one with tags)\n   - Perform client-side intersection of results\n   - Or use a different storage backend that supports combined queries\n\n### Guidance for Users\n\n- **When using Cassandra spanstore**: Be aware that specifying `DurationMin` or `DurationMax` will cause tag filters to be ignored. Validate that `ErrDurationAndTagQueryNotSupported` is returned if both are specified (enforced in `validateQuery` at line 227-229 in reader.go).\n  \n- **For combined filtering needs**: Consider using the Badger backend, or implement client-side filtering by:\n  1. Querying with duration filters to get a candidate set of trace IDs\n  2. Fetching those traces\n  3. Filtering the results by tag values in your application code\n\n- **Query design**: Structure queries to leverage the indices available. Use `ServiceName` and `OperationName` in conjunction with duration queries for best results.\n\n## References\n\n- Implementation files:\n  - [`internal/storage/v1/cassandra/spanstore/reader.go`](../../internal/storage/v1/cassandra/spanstore/reader.go) - Query logic and duration query path\n  - [`internal/storage/v1/cassandra/spanstore/writer.go`](../../internal/storage/v1/cassandra/spanstore/writer.go) - Duration index schema and insertion logic\n  - [`internal/storage/v1/badger/spanstore/reader.go`](../../internal/storage/v1/badger/spanstore/reader.go) - Badger implementation for comparison\n\n- Cassandra documentation:\n  - [Cassandra Data Modeling](https://cassandra.apache.org/doc/latest/data_modeling/index.html)\n  - [CQL Partition Keys and Clustering Columns](https://cassandra.apache.org/doc/latest/cql/ddl.html#partition-key)\n\n- Related code:\n  - `durationIndex` constant (writer.go line 47-50): CQL insert statement\n  - `queryByDuration` constant (reader.go line 51-55): CQL select statement\n  - `durationBucketSize` constant (writer.go line 57): Hourly bucketing\n  - Error `ErrDurationAndTagQueryNotSupported` (reader.go line 77): Validation that prevents combining duration and tag queries\n"
  },
  {
    "path": "docs/adr/002-mcp-server.md",
    "content": "# MCP Server Extension for Jaeger\n\n* **Status**: Proposed\n* **Date**: 2026-01-23\n\n## Context\n\nLarge Language Models (LLMs) are increasingly being used as assistants for debugging and analyzing distributed systems. Jaeger, as a distributed tracing platform, contains rich observability data that could help LLMs diagnose issues in microservice architectures. However, distributed traces can be massive—a single trace might contain hundreds or thousands of spans—and loading full trace data directly into an LLM's context window is impractical and often counterproductive.\n\nThe [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that facilitates integration between LLM applications and external data sources. MCP defines a structured way for AI agents to discover and invoke tools, access resources, and receive responses in a format optimized for LLM consumption.\n\n### Progressive Disclosure Architecture\n\nThe key insight driving this design is **progressive disclosure**: rather than dumping entire traces into an LLM context, we provide tools that allow the LLM to follow a guided \"drill-down\" workflow:\n\n1. **Search** → Find candidate traces matching specific criteria (service, time range, attributes, duration)\n2. **Map** → Visualize trace structure (topology) without loading attribute data\n3. **Diagnose** → Identify the critical execution path that contributed to latency\n4. **Inspect** → Load full details only for specific, suspicious spans\n\nThis approach prevents context-window exhaustion and forces structured reasoning.\n\n### Dependencies\n\nThe official MCP Go SDK is available at [`github.com/modelcontextprotocol/go-sdk`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp), maintained in collaboration with Google. This SDK supports:\n- Tool registration with JSON schema validation\n- StdIO and HTTP transports (including Streamable HTTP)\n- Server-Sent Events (SSE) for real-time updates\n- Concurrent session management\n\n### Existing Infrastructure\n\nJaeger already provides most of the backend functionality needed:\n\n| MCP Tool | Existing Jaeger Component | Notes |\n|----------|---------------------------|-------|\n| `get_services` | `QueryService.GetServices()` | Direct mapping |\n| `search_traces` | `QueryService.FindTraces()` | Returns metadata; needs filtering |\n| `get_trace_topology` | `QueryService.GetTrace()` | Needs post-processing to strip attributes |\n| `get_span_details` | `QueryService.GetTrace()` | Needs span-level filtering |\n| `get_trace_errors` | `QueryService.GetTrace()` | Needs error status filtering |\n| `get_critical_path` | **Not available in backend** | Only exists in UI (TypeScript) |\n\n> [!IMPORTANT]\n> The critical path algorithm currently exists only in the Jaeger UI codebase ([`jaeger-ui/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx`](../../jaeger-ui/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx)). This algorithm must be re-implemented in Go for the MCP server.\n\n### Extension Architecture\n\nFollowing the pattern established by `jaegerquery`, the MCP server will be implemented as an OpenTelemetry Collector extension. This provides:\n- Lifecycle management (Start/Shutdown)\n- Configuration validation\n- Dependency injection via `jaegerquery` extension\n- Separate HTTP/SSE endpoint for MCP protocol\n\n> [!NOTE]\n> **Phase 2 Requirement**: The MCP extension will need to retrieve the `QueryService` instance from the `jaegerquery` extension. This will require `jaegerquery` to expose `QueryService` through an Extension interface, similar to how `jaegerstorage` exposes storage factories via the `jaegerstorage.Extension` interface and `GetTraceStoreFactory()` helper function. See `cmd/jaeger/internal/exporters/storageexporter/exporter.go:35` for reference implementation pattern.\n\n## Decision\n\nImplement an MCP server as a new extension under `cmd/jaeger/internal/extension/mcpserver/` that:\n\n1. **Exposes MCP tools** for trace search, topology viewing, critical path analysis, and span inspection\n2. **Runs on a separate HTTP port** (default: 4320) with Streamable HTTP transport\n3. **Depends on `jaegerstorage`** for trace data access, similar to `jaegerquery`\n4. **Implements critical path algorithm** in Go, ported from the UI's TypeScript implementation\n5. **Uses progressive disclosure** to minimize token consumption in LLM contexts\n\n### MCP Tools Specification\n\n```yaml\ntools:\n  - name: get_services\n    description: List available service names. Use this first to discover valid service names for search_traces.\n    input_schema:\n      pattern: string (optional) - Filter services by pattern (substring match). Future: may support regex or semantic search.\n      limit: integer (optional, default: 100) - Maximum number of services to return\n    output: List of service names (strings)\n\n  - name: get_span_names\n    description: List available span names for a service. Useful for discovering valid span names before using search_traces.\n    input_schema:\n      service_name: string (required) - Filter by service name. Use get_services to discover valid names.\n      pattern: string (optional) - Optional regex pattern to filter span names\n      span_kind: string (optional) - Optional span kind filter (e.g., SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL)\n      limit: integer (optional, default: 100) - Maximum number of span names to return\n    output: List of span names with span kind information\n\n  - name: search_traces\n    description: Find traces matching service, time, attributes, and duration criteria. Returns metadata only.\n    input_schema:\n      start_time_min: string (optional, default: \"-1h\") - Start of time interval. Supports RFC3339 or relative (e.g., \"-1h\", \"-30m\")\n      start_time_max: string (optional) - End of time interval. Supports RFC3339 or relative (e.g., \"now\", \"-1m\"). Default: now\n      service_name: string (required) - Filter by service name. Use get_services to discover valid names.\n      span_name: string (optional) - Filter by span name. Use get_span_names to discover valid names.\n      attributes: object (optional) - Key-value pairs to match against span/resource attributes (e.g., {\"http.status_code\": \"500\"})\n      with_errors: boolean (optional) - If true, only return traces containing error spans\n      duration_min: duration string (optional, e.g., \"2s\", \"100ms\")\n      duration_max: duration string (optional)\n      limit: integer (default: 10, max: 100)\n    output: List of trace summaries (trace_id, service_count, span_count, duration, has_errors)\n\n  - name: get_trace_topology\n    description: Get the structural tree of a trace showing parent-child relationships, timing, and error locations. Does NOT return attributes or logs.\n    input_schema:\n      trace_id: string (required)\n      depth: integer (optional, default: 3) - Maximum depth of the tree. 0 for full tree.\n    output: Tree structure with span metadata (id, service, span_name, duration, error flag, children[])\n\n  - name: get_critical_path\n    description: Identify the sequence of spans forming the critical latency path (the blocking execution path).\n    input_schema:\n      trace_id: string (required)\n    output: Ordered list of spans on the critical path with timing information\n\n  - name: get_span_details\n    description: Fetch full details (attributes, events, links, status) for specific spans.\n    input_schema:\n      trace_id: string (required)\n      span_ids: string[] (required, max 20)\n    output: Full OTLP span data for requested spans\n\n  - name: get_trace_errors\n    description: Get full details for all spans with error status.\n    input_schema:\n      trace_id: string (required)\n    output: Full OTLP span data for error spans only\n```\n\n### Sample Tool Outputs\n\n#### get_services\n\nReturns available service names for use in `search_traces`.\n\n**Input:**\n```json\n{\n  \"pattern\": \"payment\",        // optional: substring filter\n  \"limit\": 100                 // optional: max results (default: 100)\n}\n```\n\n**Output:**\n```json\n{\n  \"services\": [\"payment-service\", \"payment-gateway\", \"payment-processor\"]\n}\n```\n\n---\n\n#### search_traces\n\nFind traces matching criteria. Returns lightweight metadata only (no attributes/events).\n\n**Input:**\n```json\n{\n  \"start_time_min\": \"-1h\",           // required: RFC3339 or relative\n  \"start_time_max\": \"now\",           // optional: default \"now\"\n  \"service_name\": \"frontend\",        // required\n  \"span_name\": \"/api/checkout\",      // optional\n  \"attributes\": {                    // optional: match span/resource attributes\n    \"http.status_code\": \"500\",\n    \"user.id\": \"12345\"\n  },\n  \"with_errors\": true,               // optional: filter to error traces\n  \"duration_min\": \"2s\",              // optional\n  \"duration_max\": \"10s\",             // optional\n  \"limit\": 10                        // optional: default 10, max 100\n}\n```\n\n**Output:**\n```json\n{\n  \"traces\": [\n    {\n      \"trace_id\": \"1a2b3c4d5e6f7890\",\n      \"root_service\": \"frontend\",\n      \"root_span_name\": \"/api/checkout\",\n      \"start_time\": \"2024-01-15T10:30:00Z\",\n      \"duration_ms\": 2450,\n      \"span_count\": 47,\n      \"service_count\": 8,\n      \"has_errors\": true\n    }\n  ]\n}\n```\n\n---\n\n#### get_trace_topology\n\nReturns the structural skeleton of a trace—parent-child relationships, timing, and error locations—**without** loading attributes or events. This keeps the response small for LLM context.\n\n**Input:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\"\n}\n```\n\n**Output:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\",\n  \"root\": {\n    \"span_id\": \"span_A\",\n    \"service\": \"frontend\",\n    \"span_name\": \"/api/checkout\",\n    \"start_time\": \"2024-01-15T10:30:00Z\",\n    \"duration_ms\": 2450,\n    \"status\": \"OK\",\n    \"children\": [\n      {\n        \"span_id\": \"span_B\",\n        \"service\": \"cart-service\",\n        \"span_name\": \"getCart\",\n        \"start_time\": \"2024-01-15T10:30:00.050Z\",\n        \"duration_ms\": 120,\n        \"status\": \"OK\",\n        \"children\": []\n      },\n      {\n        \"span_id\": \"span_C\",\n        \"service\": \"payment-service\",\n        \"span_name\": \"processPayment\",\n        \"start_time\": \"2024-01-15T10:30:00.200Z\",\n        \"duration_ms\": 2200,\n        \"status\": \"ERROR\",\n        \"children\": [\n          {\n            \"span_id\": \"span_D\",\n            \"service\": \"payment-gateway\",\n            \"span_name\": \"chargeCard\",\n            \"start_time\": \"2024-01-15T10:30:00.250Z\",\n            \"duration_ms\": 2100,\n            \"status\": \"ERROR\",\n            \"children\": []\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n---\n\n#### get_critical_path\n\nReturns the sequence of spans that form the critical latency path—the \"blocking\" execution path that directly contributed to total trace duration.\n\n**Input:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\"\n}\n```\n\n**Output:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\",\n  \"total_duration_ms\": 2450,\n  \"critical_path_duration_ms\": 2400,\n  \"path\": [\n    {\n      \"span_id\": \"span_A\",\n      \"service\": \"frontend\",\n      \"span_name\": \"/api/checkout\",\n      \"self_time_ms\": 50,\n      \"section_start_ms\": 0,\n      \"section_end_ms\": 50\n    },\n    {\n      \"span_id\": \"span_C\",\n      \"service\": \"payment-service\",\n      \"span_name\": \"processPayment\",\n      \"self_time_ms\": 100,\n      \"section_start_ms\": 50,\n      \"section_end_ms\": 150\n    },\n    {\n      \"span_id\": \"span_D\",\n      \"service\": \"payment-gateway\",\n      \"span_name\": \"chargeCard\",\n      \"self_time_ms\": 2100,\n      \"section_start_ms\": 150,\n      \"section_end_ms\": 2250\n    },\n    {\n      \"span_id\": \"span_A\",\n      \"service\": \"frontend\",\n      \"span_name\": \"/api/checkout\",\n      \"self_time_ms\": 200,\n      \"section_start_ms\": 2250,\n      \"section_end_ms\": 2450\n    }\n  ]\n}\n```\n\n> [!NOTE]\n> A span may appear multiple times on the critical path (e.g., `span_A` above) if it has work both before and after its children execute.\n\n---\n\n#### get_span_details\n\nFetch full OTLP span data for specific spans. Use this only after identifying suspicious spans via topology or critical path.\n\n**Input:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\",\n  \"span_ids\": [\"span_C\", \"span_D\"]   // max 20 spans\n}\n```\n\n**Output:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\",\n  \"spans\": [\n    {\n      \"span_id\": \"span_C\",\n      \"trace_id\": \"1a2b3c4d5e6f7890\",\n      \"parent_span_id\": \"span_A\",\n      \"service\": \"payment-service\",\n      \"span_name\": \"processPayment\",\n      \"start_time\": \"2024-01-15T10:30:00.200Z\",\n      \"duration_ms\": 2200,\n      \"status\": {\n        \"code\": \"ERROR\",\n        \"message\": \"Upstream service timeout\"\n      },\n      \"attributes\": {\n        \"http.method\": \"POST\",\n        \"http.url\": \"http://payment-gateway/charge\",\n        \"http.status_code\": \"504\",\n        \"retry.count\": \"3\"\n      },\n      \"events\": [\n        {\n          \"name\": \"retry_attempt\",\n          \"timestamp\": \"2024-01-15T10:30:00.700Z\",\n          \"attributes\": {\"attempt\": \"1\"}\n        },\n        {\n          \"name\": \"retry_attempt\",\n          \"timestamp\": \"2024-01-15T10:30:01.200Z\",\n          \"attributes\": {\"attempt\": \"2\"}\n        }\n      ],\n      \"links\": []\n    },\n    {\n      \"span_id\": \"span_D\",\n      \"trace_id\": \"1a2b3c4d5e6f7890\",\n      \"parent_span_id\": \"span_C\",\n      \"service\": \"payment-gateway\",\n      \"span_name\": \"chargeCard\",\n      \"start_time\": \"2024-01-15T10:30:00.250Z\",\n      \"duration_ms\": 2100,\n      \"status\": {\n        \"code\": \"ERROR\",\n        \"message\": \"Connection timeout to payment processor\"\n      },\n      \"attributes\": {\n        \"db.system\": \"postgresql\",\n        \"db.statement\": \"SELECT * FROM transactions WHERE...\",\n        \"net.peer.name\": \"payment-db.internal\",\n        \"net.peer.port\": \"5432\"\n      },\n      \"events\": [],\n      \"links\": []\n    }\n  ]\n}\n```\n\n---\n\n#### get_trace_errors\n\nShortcut to get full details for all error spans in a trace.\n\n**Input:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\"\n}\n```\n\n**Output:**\n```json\n{\n  \"trace_id\": \"1a2b3c4d5e6f7890\",\n  \"error_count\": 2,\n  \"spans\": [\n    // Same format as get_span_details output\n    // Contains only spans where status.code == \"ERROR\"\n  ]\n}\n```\n\n### Configuration\n\n```yaml\nextensions:\n  jaeger_mcp:\n    # HTTP endpoint for MCP protocol (Streamable HTTP transport)\n    http:\n      endpoint: \"0.0.0.0:4320\"\n    \n    # Storage configuration (references jaegerstorage extension)\n    storage:\n      traces: \"some_storage\"\n    \n    # Server identification for MCP protocol\n    server_name: \"jaeger\"\n    server_version: \"${version}\"\n    \n    # Limits\n    max_span_details_per_request: 20\n    max_search_results: 100\n```\n\n### Extension Directory Structure\n\n```\ncmd/jaeger/internal/extension/jaegermcp/\n├── README.md\n├── config.go            # Configuration struct and validation\n├── config_test.go\n├── factory.go           # Extension factory (NewFactory, createDefaultConfig)\n├── factory_test.go\n├── server.go            # Extension lifecycle (Start, Shutdown, Dependencies)\n├── server_test.go\n└── internal/\n    ├── criticalpath/    # Critical path algorithm (ported from UI)\n    │   ├── criticalpath.go\n    │   └── criticalpath_test.go\n    ├── handlers/        # MCP tool handlers\n    │   ├── search_traces.go\n    │   ├── search_traces_test.go\n    │   ├── get_trace_topology.go\n    │   ├── get_critical_path.go\n    │   ├── get_span_details.go\n    │   ├── get_span_details_test.go\n    │   ├── get_trace_errors.go\n    │   └── get_trace_errors_test.go\n    └── types/           # Response types for MCP tools (one file per handler)\n        ├── search_traces.go\n        ├── get_span_details.go\n        └── get_trace_errors.go\n```\n\n## Consequences\n\n### Positive\n\n1. **AI Integration**: Enables LLM-based assistants to query and analyze Jaeger traces efficiently\n2. **Token Optimization**: Progressive disclosure architecture prevents context exhaustion\n3. **Standards Compliance**: Uses official MCP protocol, compatible with Claude, GPT, and other MCP-enabled agents\n4. **Reusable Algorithm**: Go implementation of critical path can be used for API responses, not just MCP\n5. **Clean Separation**: Runs on separate port, doesn't affect existing query service\n\n### Negative\n\n1. **Algorithm Duplication**: Critical path algorithm exists in both TypeScript (UI) and Go (MCP server)\n2. **New Dependency**: Adds `github.com/modelcontextprotocol/go-sdk` to dependencies\n3. **Maintenance Overhead**: Additional extension to maintain and test\n\n### Mitigation\n\n- Consider eventually exposing critical path via the gRPC query API for UI consumption, eliminating the TypeScript implementation\n- The MCP SDK is official and well-maintained; dependency risk is low\n- Extension follows established patterns, reducing maintenance burden\n\n---\n\n## Implementation Roadmap\n\n### Phase 1: Foundation\n\n1. **Extension Scaffold** ✅\n   - Create `jaegermcp` extension directory structure\n   - Implement `config.go` with configuration validation\n   - Implement `factory.go` following `jaegerquery` pattern\n   - Implement `server.go` with lifecycle management\n   - Wire extension into component registration\n\n2. **MCP Server Setup** ✅\n   - Add `github.com/modelcontextprotocol/go-sdk` dependency\n   - Initialize MCP server with Streamable HTTP transport\n   - Implement server start/shutdown with graceful cleanup\n\n---\n\n### Phase 2: Basic Tools\n\n3. **Storage Integration** ✅\n   - Connect to `jaegerstorage` extension for trace reader access\n   - Create internal service layer for trace operations\n\n4. **Implement `get_services` Tool** ✅\n   - Wrap `QueryService.GetServices()`\n   - Support optional regex pattern filtering\n   - Apply configurable limit (default: 100)\n   - Return list of service names\n\n4b. **Implement `get_span_names` Tool** ✅\n   - Wrap `QueryService.GetOperations()`\n   - Support optional regex pattern filtering\n   - Support optional span kind filtering\n   - Apply configurable limit (default: 100)\n   - Return list of span names with span kind information\n\n5. **Implement `search_traces` Tool** ✅\n   - Wrap `QueryService.FindTraces()`\n   - Transform response to MCP-optimized format (metadata only)\n   - Add input validation and error handling\n\n6. **Implement `get_span_details` Tool** ✅\n   - Wrap `QueryService.GetTrace()`\n   - Filter to requested span IDs only\n   - Return full OTLP attribute data\n\n7. **Implement `get_trace_errors` Tool** ✅\n   - Wrap `QueryService.GetTrace()`\n   - Filter to spans with error status\n   - Return full OTLP attribute data\n\n---\n\n### Phase 3: Advanced Tools\n\n7. **Implement `get_trace_topology` Tool** ✅\n   - Fetch trace via `QueryService.GetTrace()`\n   - Build tree structure from flat span list\n   - **Strip attributes and events** before response\n   - Include timing and error flags\n\n8. **Port Critical Path Algorithm** ✅\n   - Study TypeScript implementation in `jaeger-ui/packages/jaeger-ui/src/components/TracePage/CriticalPath/`\n   - Implement equivalent Go algorithm in `internal/criticalpath/`\n   - Key components:\n     - `findLastFinishingChildSpan()` - Find LFC for a span\n     - `sanitizeOverFlowingChildren()` - Handle child spans that exceed parent duration\n     - `computeCriticalPath()` - Main recursive algorithm\n   - Add comprehensive unit tests with same test cases as UI\n\n9. **Implement `get_critical_path` Tool** ✅\n   - Use critical path algorithm from step 8\n   - Return ordered list of spans on critical path\n   - Include timing breakdown\n\n---\n\n### Phase 4: Polish and Extend\n\n10. **Configuration and Observability**\n    - Add OpenTelemetry metrics for MCP tool invocations\n    - Add structured logging for debugging\n    - Implement rate limiting if needed\n\n11. **Documentation**\n    - Write `README.md` for the extension\n    - Document MCP server instructions (system prompt) for LLM configuration\n    - Add example configurations\n\n12. **Integration Testing**\n    - End-to-end tests with mock storage\n    - Test MCP protocol compliance\n    - Performance testing with large traces\n\n---\n\n## Testing Strategy\n\n### Unit Tests\n\n| Component | Testing Approach |\n|-----------|------------------|\n| `config.go` | Test validation with valid/invalid configs |\n| `factory.go` | Test factory creation and default config |\n| `server.go` | Test lifecycle with mock storage extension |\n| Critical path algorithm | Port test cases from TypeScript tests; use same expected results |\n| Tool handlers | Mock `QueryService`; test input validation, response format, error handling |\n\n### Integration Tests\n\n1. **Extension Lifecycle**\n   - Test extension starts with valid configuration\n   - Test graceful shutdown\n   - Test dependency resolution with `jaegerstorage`\n\n2. **MCP Protocol Compliance**\n   - Use MCP SDK client to connect to server\n   - Verify tool discovery (`tools/list`)\n   - Verify tool invocation and response format\n   - Test error handling (invalid inputs, missing traces)\n\n3. **End-to-End Scenarios**\n   - Use memory storage with sample traces\n   - Execute progressive disclosure workflow (search → topology → critical path → details)\n   - Verify token efficiency (topology response is smaller than full trace)\n\n### Test Fixtures\n\nReuse existing test fixtures from:\n- `cmd/jaeger/internal/extension/jaegerquery/internal/fixture/` - Sample traces\n- `jaeger-ui/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/` - Critical path test cases\n\n### CI Integration\n\n- Add to existing CI workflow\n- Include in `make test` target\n- Add to code coverage requirements\n\n---\n\n## References\n\n- [Model Context Protocol Specification](https://modelcontextprotocol.io/)\n- [MCP Go SDK Documentation](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp)\n- Jaeger Extension Pattern: [`cmd/jaeger/internal/extension/jaegerquery/`](../../cmd/jaeger/internal/extension/jaegerquery/)\n- Critical Path UI Implementation: [`jaeger-ui/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx`](../../jaeger-ui/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx)\n- Original Design Document: [`design.md`](../../design.md)\n"
  },
  {
    "path": "docs/adr/003-lazy-storage-factory-initialization.md",
    "content": "# Lazy Storage Factory Initialization\n\n* **Status**: Implemented -- https://github.com/jaegertracing/jaeger/pull/7887\n* **Date**: 2026-01-20\n\n## Context\n\nThe `jaegerstorage` extension (`cmd/jaeger/internal/extension/jaegerstorage/extension.go`) is responsible for managing storage backends in Jaeger. Its configuration allows declaring arbitrary numbers of storage backends under `trace_backends` and `metric_backends`. However, not all configured storages are necessarily used: consumers request specific storages by name via `TraceStorageFactory(name)`.\n\n### Current Behavior\n\nCurrently, the extension initializes **all** configured storage factories during `Start()`:\n\n```go\nfunc (s *storageExt) Start(ctx context.Context, host component.Host) error {\n    // ...\n    for storageName, cfg := range s.config.TraceBackends {\n        factory, err := storageconfig.CreateTraceStorageFactory(...)  // Connects immediately\n        s.factories[storageName] = factory\n    }\n    // ...\n}\n```\n\nEach factory's `NewFactory()` function performs actual initialization:\n\n| Backend       | Initialization Actions                                           |\n|---------------|------------------------------------------------------------------|\n| Cassandra     | Creates session, connects to cluster, optionally creates schema  |\n| Elasticsearch | Creates HTTP client, establishes connection pool                 |\n| ClickHouse    | Opens connection, pings server, optionally creates schema        |\n| gRPC          | Establishes gRPC connections (reader and writer)                 |\n| Badger        | Opens database files, starts background maintenance goroutines   |\n| Memory        | Allocates in-memory store                                        |\n\n### Problems\n\n1. **Wasted Resources**: Storage backends that are configured but never used still consume connections, memory, and background goroutines.\n\n2. **Startup Failures for Unused Backends**: If a configured backend is unavailable (e.g., Cassandra cluster down), the entire extension fails to start, even if that storage isn't actually needed by any pipeline component.\n\n3. **Configuration Use Cases**: Users may want to define multiple storage backends in a shared configuration file and selectively enable them in different deployment scenarios without modifying the configuration.\n\n### Real-World Scenario\n\nConsider a configuration with:\n```yaml\nextensions:\n  jaeger_storage:\n    trace_backends:\n      primary_es:\n        elasticsearch: { ... }\n      archive_cassandra:\n        cassandra: { ... }\n      debug_memory:\n        memory: { max_traces: 10000 }\n\n  jaeger_query:\n    storage:\n      traces: primary_es\n      traces_archive: debug_memory\n```\n\nWith current behavior, Jaeger attempts to connect to Cassandra at startup, even though `archive_cassandra` isn't used. If Cassandra is unavailable, Jaeger fails to start despite the primary storage (Elasticsearch) being fully operational.\n\n## Decision\n\nThis ADR evaluates two approaches to implement lazy initialization.\n\n---\n\n## Option 1: Two-Phase Factory Framework (Configure + Initialize)\n\n### Design\n\nRefactor the factory framework to separate configuration validation from backend initialization:\n\n```go\n// New interface additions to tracestore.Factory\ntype ConfigurableFactory interface {\n    Factory\n    // Configure validates configuration without establishing connections.\n    // Called during extension Start() for all configured backends.\n    Configure(ctx context.Context) error\n}\n\ntype InitializableFactory interface {\n    Factory\n    // Initialize establishes connections and allocates resources.\n    // Called lazily when TraceStorageFactory() is invoked.\n    Initialize(ctx context.Context) error\n    // IsInitialized returns true if Initialize() has been called.\n    IsInitialized() bool\n}\n```\n\n### Extension Changes\n\n```go\ntype storageExt struct {\n    config           *Config\n    telset           component.TelemetrySettings\n    factories        map[string]tracestore.Factory\n    initialized      map[string]bool\n    initMu           sync.Mutex  // Serializes initialization\n    // ...\n}\n\nfunc (s *storageExt) Start(ctx context.Context, host component.Host) error {\n    for storageName, cfg := range s.config.TraceBackends {\n        // Phase 1: Configuration only - validate without connecting\n        factory, err := storageconfig.CreateUninitializedFactory(ctx, storageName, cfg, telset)\n        if err != nil {\n            return fmt.Errorf(\"invalid configuration for storage '%s': %w\", storageName, err)\n        }\n        if configurable, ok := factory.(ConfigurableFactory); ok {\n            if err := configurable.Configure(ctx); err != nil {\n                return fmt.Errorf(\"configuration validation failed for storage '%s': %w\", storageName, err)\n            }\n        }\n        s.factories[storageName] = factory\n    }\n    return nil\n}\n\nfunc (s *storageExt) TraceStorageFactory(name string) (tracestore.Factory, bool) {\n    s.initMu.Lock()\n    defer s.initMu.Unlock()\n\n    f, ok := s.factories[name]\n    if !ok {\n        return nil, false\n    }\n\n    // Phase 2: Lazy initialization on first access\n    if !s.initialized[name] {\n        if initializable, ok := f.(InitializableFactory); ok {\n            if err := initializable.Initialize(context.Background()); err != nil {\n                s.telset.Logger.Error(\"Failed to initialize storage\",\n                    zap.String(\"name\", name), zap.Error(err))\n                return nil, false\n            }\n        }\n        s.initialized[name] = true\n    }\n    return f, true\n}\n```\n\n### Factory Implementation Changes\n\nEach factory needs refactoring. Example for Cassandra:\n\n```go\ntype Factory struct {\n    config     cassandra.Options\n    telset     telemetry.Settings\n    session    cassandra.Session  // nil until initialized\n    configured bool\n    initialized bool\n}\n\nfunc NewFactory(opts cassandra.Options, telset telemetry.Settings) (*Factory, error) {\n    return &Factory{\n        config: opts,\n        telset: telset,\n    }, nil\n}\n\nfunc (f *Factory) Configure(ctx context.Context) error {\n    // Validate configuration without connecting\n    if err := f.config.Configuration.Validate(); err != nil {\n        return err\n    }\n    f.configured = true\n    return nil\n}\n\nfunc (f *Factory) Initialize(ctx context.Context) error {\n    if f.initialized {\n        return nil\n    }\n    // Establish actual connection\n    session, err := cassandra.NewSession(&f.config.Configuration)\n    if err != nil {\n        return err\n    }\n    f.session = session\n    f.initialized = true\n    return nil\n}\n\nfunc (f *Factory) IsInitialized() bool {\n    return f.initialized\n}\n```\n\n### Pros\n\n1. **Early Configuration Validation**: Invalid configurations are caught at startup, even for unused storages. This prevents runtime surprises.\n\n2. **Clear Separation of Concerns**: Configuration validation and resource initialization are distinct phases with clear semantics.\n\n3. **Predictable Startup Behavior**: All configuration errors surface during `Start()`, making debugging easier.\n\n4. **Consistent Interface**: All factories follow the same lifecycle pattern.\n\n### Cons\n\n1. **Significant Refactoring**: All 6+ factory implementations require changes:\n   - `internal/storage/v2/cassandra/factory.go`\n   - `internal/storage/v2/elasticsearch/factory.go`\n   - `internal/storage/v2/clickhouse/factory.go`\n   - `internal/storage/v2/grpc/factory.go`\n   - `internal/storage/v2/badger/factory.go`\n   - `internal/storage/v2/memory/factory.go`\n   - `cmd/internal/storageconfig/factory.go`\n\n2. **API Breaking Change**: The `tracestore.Factory` interface changes, potentially affecting external consumers.\n\n3. **Complex State Management**: Factories must track configuration vs. initialization state.\n\n4. **Testing Complexity**: Tests need to account for the two-phase lifecycle.\n\n### Implementation Effort\n\n| Component | Effort |\n|-----------|--------|\n| Interface definitions | Low |\n| Extension refactoring | Medium |\n| Cassandra factory | Medium |\n| Elasticsearch factory | Medium |\n| ClickHouse factory | Medium |\n| gRPC factory | Medium |\n| Badger factory | Medium |\n| Memory factory | Low |\n| storageconfig helper | Medium |\n| Test updates | High |\n| **Total** | **High** |\n\n---\n\n## Option 2: Simple Lazy Initialization (Defer Everything)\n\n### Design\n\nMove all factory creation to `TraceStorageFactory()` without modifying the factory interfaces:\n\n```go\ntype storageExt struct {\n    config           *Config\n    telset           component.TelemetrySettings\n    host             component.Host\n    factories        map[string]tracestore.Factory\n    factoryMu        sync.Mutex\n    // ...\n}\n\nfunc (s *storageExt) Start(ctx context.Context, host component.Host) error {\n    s.host = host  // Store for later use\n    s.factories = make(map[string]tracestore.Factory)\n    // No factory initialization - just validation that config keys exist\n    return nil\n}\n\n// Changed signature: (Factory, bool) -> (Factory, error)\n// This allows callers to distinguish \"not configured\" from \"initialization failed\"\nfunc (s *storageExt) TraceStorageFactory(name string) (tracestore.Factory, error) {\n    s.factoryMu.Lock()\n    defer s.factoryMu.Unlock()\n\n    // Return cached factory if already created\n    if f, ok := s.factories[name]; ok {\n        return f, nil\n    }\n\n    // Check if configuration exists\n    cfg, ok := s.config.TraceBackends[name]\n    if !ok {\n        return nil, fmt.Errorf(\n            \"storage '%s' not declared in '%s' extension configuration\",\n            name, componentType,\n        )\n    }\n\n    // Create factory on demand\n    telset := telemetry.FromOtelComponent(s.telset, s.host)\n    factory, err := storageconfig.CreateTraceStorageFactory(\n        context.Background(),\n        name,\n        cfg,\n        telset,\n        func(authCfg config.Authentication, backendType, backendName string) (extensionauth.HTTPClient, error) {\n            return s.resolveAuthenticator(s.host, authCfg, backendType, backendName)\n        },\n    )\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to initialize storage '%s': %w\", name, err)\n    }\n\n    s.factories[name] = factory\n    return factory, nil\n}\n```\n\n### Pros\n\n1. **Minimal Code Changes**: Only `extension.go` and its callers need modification.\n\n2. **No Factory Interface Changes**: Existing factory implementations (`tracestore.Factory`) remain unchanged.\n\n3. **No External API Breaking Changes**: The extension is internal; external consumers are unaffected.\n\n4. **Simple Mental Model**: Factories are created when needed, cached for reuse.\n\n5. **Quick Implementation**: Can be completed in a single PR.\n\n6. **Clear Error Messages**: Changing signature from `(Factory, bool)` to `(Factory, error)` allows callers to distinguish \"storage not configured\" from \"initialization failed\" and provides actionable error messages.\n\n### Cons\n\n1. **Deferred Configuration Errors**: Invalid configurations for unused storages are never detected. A typo in an unused backend's config silently passes.\n\n2. **Runtime Initialization Failures**: Connection failures happen when a pipeline component first requests the storage, not at startup. This could cause unexpected failures during operation.\n\n3. **Less Predictable Startup**: Startup succeeds even with broken configurations, potentially masking issues.\n\n4. **Interface Signature Change**: The `Extension` interface methods change from `(Factory, bool)` to `(Factory, error)`. While not a breaking change for external consumers (the extension is internal), it requires updating all callers within the codebase.\n\n### Potential Mitigation\n\nAdd optional configuration validation at startup:\n\n```go\nfunc (s *storageExt) Start(ctx context.Context, host component.Host) error {\n    s.host = host\n    // Optional: validate configurations without initializing\n    for name, cfg := range s.config.TraceBackends {\n        if err := cfg.Validate(); err != nil {\n            return fmt.Errorf(\"invalid configuration for storage '%s': %w\", name, err)\n        }\n    }\n    return nil\n}\n```\n\nThis requires adding `Validate()` methods to backend configs, which is simpler than full two-phase factories.\n\n### Implementation Effort\n\n| Component | Effort |\n|-----------|--------|\n| Extension interface change | Low |\n| Extension lazy init logic | Low |\n| Update callers (GetTraceStoreFactory, etc.) | Low |\n| Config validation (optional) | Low-Medium |\n| Test updates | Low |\n| **Total** | **Low** |\n\n---\n\n## Comparison Summary\n\n| Criterion | Option 1: Two-Phase | Option 2: Simple Lazy |\n|-----------|--------------------|-----------------------|\n| Implementation effort | High | Low |\n| Factory interface changes | Yes (breaking) | No |\n| Extension interface changes | No | Yes (minor) |\n| Early config validation | Yes | Partial (with mitigation) |\n| Runtime failure risk | Low | Medium |\n| Code complexity | Higher | Lower |\n| Factory changes required | All backends | None |\n| Error message clarity | Good | Good (with error return) |\n| Time to implement | Weeks | Days |\n\n## Recommendation\n\n**Option 2 (Simple Lazy Initialization) with Config Validation Mitigation** is recommended as the initial implementation because:\n\n1. It solves the primary problem (wasted resources, startup failures for unused backends) with minimal risk.\n2. It can be implemented quickly without breaking changes.\n3. Adding `Validate()` methods to configs provides early error detection without the complexity of two-phase factories.\n4. Option 1 can be pursued later if stronger guarantees are needed.\n\n### Suggested Implementation Steps\n\n1. Change `Extension` interface signatures from `(Factory, bool)` to `(Factory, error)` for `TraceStorageFactory()` and `MetricStorageFactory()`.\n2. Refactor `extension.go` to defer factory creation to `TraceStorageFactory()`/`MetricStorageFactory()`.\n3. Update all callers (`GetTraceStoreFactory`, `GetMetricStorageFactory`, `GetSamplingStoreFactory`, `GetPurger`) to handle the new error return.\n4. Add `Validate()` methods to `TraceBackend` and `MetricBackend` config structs.\n5. Call validation in `Start()` to catch configuration errors early.\n6. Update tests to verify lazy initialization behavior.\n7. Document the behavior change in release notes.\n\n## Consequences\n\n### Positive\n\n- Unused storage backends no longer consume resources.\n- Startup succeeds even when unused backends are unavailable.\n- Minimal code changes reduce risk of regressions.\n\n### Negative\n\n- Configuration errors for unused storages may go unnoticed (mitigated by validation).\n- First access to a storage may fail if the backend becomes unavailable after startup.\n- Requires updating all callers to handle the new `error` return type.\n\n### Neutral\n\n- Logging will shift from startup to first-access for storage initialization messages.\n- Shutdown logic must handle partially-initialized factory maps.\n\n---\n\n## References\n\n- Extension implementation: `cmd/jaeger/internal/extension/jaegerstorage/extension.go`\n- Factory creation: `cmd/internal/storageconfig/factory.go`\n- Factory interface: `internal/storage/v2/api/tracestore/factory.go`\n- Backend implementations:\n  - `internal/storage/v2/cassandra/factory.go`\n  - `internal/storage/v2/elasticsearch/factory.go`\n  - `internal/storage/v2/clickhouse/factory.go`\n  - `internal/storage/v2/grpc/factory.go`\n  - `internal/storage/v2/badger/factory.go`\n  - `internal/storage/v2/memory/factory.go`\n"
  },
  {
    "path": "docs/adr/004-migrating-coverage-gating-to-github-actions.md",
    "content": "# Migrate Coverage Gating from Codecov to GitHub Actions\n\n* **Status**: Accepted (implemented)\n* **Date**: 2026-03-01\n\n## Context\n\nJaeger uses [Codecov](https://codecov.io) for two functions:\n\n1. **Long-term trend tracking**: Coverage is uploaded after each CI run via the Codecov Action.\n2. **PR gating**: Codecov's GitHub status check blocks merges when coverage drops below a threshold.\n\nCoverage is collected across 11 CI jobs (unit tests + E2E), uploaded through `.github/actions/upload-codecov/action.yml`.\n\n### Problem\n\nCodecov's PR status checks suffer from latency (results lag behind CI completion) and intermittent rate-limit failures that block PRs even when coverage is healthy. The gating logic should run entirely within GitHub Actions for faster, more reliable feedback.\n\n## Decision\n\nExtend the existing `CI Summary Report` fan-in workflow to add coverage aggregation and gating alongside the existing metrics comparison. Codecov uploads are retained for long-term historical trending and per-flag breakdown views.\n\n### Requirements\n\n1. Coverage must be merged from all CI jobs (unit tests and E2E) into a single profile.\n2. Two independent gates must be applied:\n   - **Absolute floor**: total coverage ≥ 95%, matching the Codecov project target.\n   - **No regression**: total coverage must not drop compared to the `main` baseline.\n3. The merged profile must be filtered using the same exclusions as `.codecov.yml` (generated files, mocks, integration test infrastructure) so both tools report from a single source of truth.\n4. A `Coverage Gate` check-run must always be posted to the PR — even when no coverage data is available — so it can be used as a required status check in branch protection.\n5. The workflow must run for `pull_request`, `merge_group`, and `push` (to `main`) events triggered through the CI Orchestrator, as well as via manual `workflow_dispatch`.\n6. On `main`-branch runs, the coverage baseline must be cached for future PR comparisons.\n\n### Success Criteria\n\n- `Coverage Gate` and `Metrics Comparison` check-runs appear on every PR and merge-queue run.\n- Coverage regressions block PRs when `Coverage Gate` is added to required status checks.\n- Manual re-runs via `workflow_dispatch` allow re-posting checks from any branch.\n\n## Implementation Overview\n\n### Coverage Artifact Pipeline\n\nEach CI job uploads its coverage profile as a `coverage-<flag>` artifact (7-day retention) via `.github/actions/upload-codecov/action.yml`, alongside the existing Codecov upload.\n\n### Fan-in Workflow (`ci-summary-report.yml`)\n\nThe single `summary-report` job:\n\n1. **Resolves the source run** — determines the CI Orchestrator run ID (from `workflow_run` event or `workflow_dispatch` input), validates it succeeded, and extracts PR metadata (number + head SHA) via the GitHub API.\n2. **Downloads all artifacts** — uses `gh run download` to fetch all artifacts from the source run.\n3. **Merges and gates coverage** — merges all `coverage-*/*.out` profiles with `gocovmerge`, filters excluded paths, and applies the two coverage gates.\n4. **Posts results** — creates `Metrics Comparison` and `Coverage Gate` check-runs on the PR. When no coverage data exists, `Coverage Gate` reports success with a \"skipped\" note to satisfy branch protection.\n5. **Saves baseline on `main`** — caches the coverage percentage for future PR comparisons.\n\n### Key Files\n\n| File | Role |\n|------|------|\n| `.github/workflows/ci-summary-report.yml` | Fan-in workflow |\n| `.github/actions/upload-codecov/action.yml` | Coverage artifact upload |\n| `.github/workflows/ci-orchestrator.yml` | Triggers the fan-in |\n| `scripts/e2e/filter_coverage.py` | Applies `.codecov.yml` exclusions |\n| `internal/tools/tools.go` | `gocovmerge` tool dependency |\n| `.codecov.yml` | Single source of truth for ignore patterns |\n\n## Consequences\n\n### Positive\n\n- **Faster feedback**: coverage gate result appears as soon as the CI Orchestrator completes.\n- **Reliability**: eliminates Codecov rate-limit failures blocking PRs.\n- **Consolidated reporting**: performance metrics and coverage appear in a single sticky PR comment.\n- **Required status check safe**: `Coverage Gate` is always created, even when coverage is skipped.\n\n### Negative\n\n- **Artifact storage cost**: `coverage-*` artifacts add ~50–100 MB per CI run (7-day retention).\n- **One tool dependency**: `github.com/wadey/gocovmerge` in `internal/tools/go.mod`.\n\n### Neutral\n\n- Codecov remains active for long-term trending; removing it can be a follow-up decision.\n\n## References\n\n- [CI Summary Report workflow](/.github/workflows/ci-summary-report.yml)\n- [Coverage upload action](/.github/actions/upload-codecov/action.yml)\n- [CI Orchestrator](/.github/workflows/ci-orchestrator.yml)\n- [Coverage filter script](/scripts/e2e/filter_coverage.py)\n- [Tool registry](/internal/tools/tools.go)\n- [Coverage policy](/.codecov.yml)\n"
  },
  {
    "path": "docs/adr/005-badger-storage-record-layouts.md",
    "content": "# Badger Storage Record Layouts\n\n* **Status**: Documented existing implementation\n* **Date**: 2026-03-12\n\n## Context\n\nJaeger supports [Badger](https://github.com/dgraph-io/badger) as an embedded, local key-value store backend. Badger is primarily intended for all-in-one deployments where a lightweight storage solution is desirable without an external database dependency. Because Badger is a generic sorted key-value store, Jaeger must impose its own logical record structure on top of it.\n\nThis ADR documents the record layouts used in the Badger storage implementation as they exist today, covering key formats, value formats, and the overall design rationale. The intent is to make the storage design visible to contributors and to serve as a reference when reasoning about query behavior or considering future changes.\n\nThe implementation lives in:\n- [`internal/storage/v1/badger/spanstore/writer.go`](../../internal/storage/v1/badger/spanstore/writer.go) — key generation and span writes\n- [`internal/storage/v1/badger/spanstore/reader.go`](../../internal/storage/v1/badger/spanstore/reader.go) — query execution and span reads\n- [`internal/storage/v1/badger/spanstore/cache.go`](../../internal/storage/v1/badger/spanstore/cache.go) — in-memory service/operation cache\n- [`internal/storage/v1/badger/samplingstore/storage.go`](../../internal/storage/v1/badger/samplingstore/storage.go) — sampling data storage\n- [`internal/storage/v1/badger/config.go`](../../internal/storage/v1/badger/config.go) — configuration and defaults\n\n### Design Principles\n\nAll keys are encoded in **big-endian** byte order. This ensures that integer values sort lexicographically in the same order as their numeric values, which is a prerequisite for the range-scan and reverse-iteration queries used throughout the implementation.\n\nAll span-related records (primary span records and all secondary indexes) share a common property: **the most significant bit of the first byte is always set** (`0x80` or higher). This cleanly separates span data from sampling data (prefixes `0x08` and `0x09`) in the keyspace.\n\nA single Badger database instance holds all record types. There is no separate \"index store\" — different logical tables are distinguished solely by the prefix byte of their keys.\n\n---\n\n## Record Layouts\n\n### Key Prefix Summary\n\n| Record type            | Prefix byte | Used by          |\n|------------------------|-------------|------------------|\n| Span (primary record)  | `0x80`      | Span store       |\n| Service name index     | `0x81`      | Span store       |\n| Operation name index   | `0x82`      | Span store       |\n| Tag index              | `0x83`      | Span store       |\n| Duration index         | `0x84`      | Span store       |\n| Throughput             | `0x08`      | Sampling store   |\n| Probabilities/QPS      | `0x09`      | Sampling store   |\n\n---\n\n### 1. Primary Span Record (`0x80`)\n\nEach span is stored as a single key-value entry.\n\n**Key** (33 bytes, fixed size):\n```\n[0x80][traceID.High: 8B][traceID.Low: 8B][startTime: 8B][spanID: 8B]\n```\n\n- `traceID.High` and `traceID.Low` — the 128-bit trace ID split into two `uint64` values\n- `startTime` — `uint64`, microseconds since Unix epoch (via `model.TimeAsEpochMicroseconds`)\n- `spanID` — `uint64`\n\n**Value**: the serialized span, encoded as either:\n- Protobuf (`proto.Marshal`, encoding type `0x02`) — the default\n- JSON (`json.Marshal`, encoding type `0x01`) — available as an alternative\n\n**`UserMeta` byte**: stores the encoding type in the lower 4 bits. Reads use `item.UserMeta() & 0x0F` to determine how to deserialize the value.\n\n**TTL**: all entries expire after the configured span TTL (default 72 hours). The expiry is set as `uint64(time.Now().Add(ttl).Unix())` (seconds since Unix epoch) in the Badger entry's `ExpiresAt` field.\n\n**Sorting behavior**: because all three components after the prefix byte are encoded in big-endian, all spans belonging to the same trace cluster together, and within a trace they are sorted by start time and then span ID. This allows `GetTrace` to retrieve all spans of a trace via a single prefix scan without any additional filtering.\n\n---\n\n### 2. Service Name Index (`0x81`)\n\nAn index entry is written for each span, keyed by the service name of the span's process.\n\n**Key** (variable size):\n```\n[0x81][serviceName: variable][startTime: 8B][traceID.High: 8B][traceID.Low: 8B]\n```\n\n- `serviceName` — UTF-8 bytes of the service name, no length prefix or separator\n- `startTime` — `uint64`, microseconds since Unix epoch\n- `traceID` — the 16-byte trace ID (High then Low, big-endian)\n\n**Value**: empty (`nil`)\n\n**Purpose**: enables scanning all trace IDs associated with a given service within a time range. The reader seeks to `[0x81][serviceName]` and iterates in reverse (latest first), extracting the trailing 16 bytes of each key as the trace ID.\n\n**TTL**: same as the corresponding primary span record.\n\n---\n\n### 3. Operation Name Index (`0x82`)\n\nAn index entry is written for each span, keyed by the concatenation of service name and operation name.\n\n**Key** (variable size):\n```\n[0x82][serviceName + operationName: variable][startTime: 8B][traceID.High: 8B][traceID.Low: 8B]\n```\n\n- `serviceName + operationName` — the two strings concatenated directly, no separator\n\n**Value**: empty (`nil`)\n\n**Purpose**: enables finding trace IDs for a specific service + operation pair within a time range.\n\n**Note**: because the service name and operation name are concatenated without a separator, a service named `\"foo\"` with operation `\"bar\"` produces the same prefix as a service named `\"foobar\"` with operation `\"\"`. The reader guards against this ambiguity by checking that the full key prefix (up to the timestamp) matches exactly.\n\n**TTL**: same as the corresponding primary span record.\n\n---\n\n### 4. Tag Index (`0x83`)\n\nFor each searchable tag key-value pair associated with a span, a separate index entry is written. Tags are indexed from three sources: `span.Tags`, `span.Process.Tags`, and `log.Fields` for each log entry.\n\n**Key** (variable size):\n```\n[0x83][serviceName + tagKey + tagValue: variable][startTime: 8B][traceID.High: 8B][traceID.Low: 8B]\n```\n\n- `serviceName + tagKey + tagValue` — all three strings concatenated directly, no separators\n- Tag values are converted to their string representation via `kv.AsString()` before being embedded in the key\n\n**Value**: empty (`nil`)\n\n**Purpose**: enables finding trace IDs for spans that carry a specific tag key-value pair within a given service.\n\n**TTL**: same as the corresponding primary span record.\n\n---\n\n### 5. Duration Index (`0x84`)\n\nOne index entry is written per span, encoding the span's duration.\n\n**Key** (variable size, fixed numeric portion):\n```\n[0x84][duration: 8B][startTime: 8B][traceID.High: 8B][traceID.Low: 8B]\n```\n\n- `duration` — `uint64`, span duration in microseconds (via `model.DurationAsMicroseconds`)\n- `startTime` — `uint64`, microseconds since Unix epoch\n- `traceID` — 16-byte trace ID\n\n**Value**: empty (`nil`)\n\n**Purpose**: enables range scans over span duration. A duration query scans forward from `[0x84][minDuration]` to `[0x84][maxDuration]`, collecting trace IDs. The result is used as a hash-set filter (`hashOuter`) that is intersected with results from other indexes before final trace retrieval.\n\n**Key design rationale**: by placing `duration` before `startTime`, all keys for a given duration value are contiguous in the sorted keyspace, making range scans efficient. The time range filter is applied as a secondary check during the scan.\n\n**TTL**: same as the corresponding primary span record.\n\n---\n\n### 6. Sampling Throughput Record (`0x08`)\n\nWritten by the adaptive sampling component to record observed request throughput.\n\n**Key** (16 bytes allocated, 9 bytes used):\n```\n[0x08][startTime: 8B][0x00 × 7]\n```\n\n- `startTime` — `uint64`, microseconds since Unix epoch\n- The remaining 7 bytes of the 16-byte allocation are implicitly zero\n\n**Value**: JSON-encoded `[]*model.Throughput`\n\n**No TTL**: sampling entries do not have an explicit expiry set via Badger's `ExpiresAt`. Cleanup relies on explicit deletion or Badger's value-log GC.\n\n---\n\n### 7. Sampling Probabilities/QPS Record (`0x09`)\n\nWritten by the adaptive sampling component to record computed sampling probabilities and QPS estimates.\n\n**Key** (16 bytes allocated, 9 bytes used):\n```\n[0x09][startTime: 8B][0x00 × 7]\n```\n\n**Value**: JSON-encoded `ProbabilitiesAndQPS` struct:\n```go\ntype ProbabilitiesAndQPS struct {\n    Hostname      string\n    Probabilities model.ServiceOperationProbabilities  // map[service]map[operation]float64\n    QPS           model.ServiceOperationQPS             // map[service]map[operation]float64\n}\n```\n\n**No TTL**: same as throughput records.\n\n---\n\n## Query Execution\n\nThe reader builds an *execution plan* that describes how to combine index results:\n\n1. **Duration filter** (if present): scanned via `scanRangeIndex` using the duration index. Results are stored in `plan.hashOuter` (a set of trace IDs) for subsequent intersection.\n\n2. **Index seeks** (service, operation, tags): for each index key prefix derived from the query parameters, `scanIndexKeys` iterates in reverse order (latest first) within the time range, extracting the trailing 16 bytes of each matching key as a trace ID.\n\n   - When multiple index seeks are needed (e.g., tag + operation), all but the last are scanned first and their results are combined via `mergeJoinIds` (a sorted merge intersection). The final seek then filters against this merged set.\n\n3. **Full table scan** (fallback, when no service name is specified): `scanTimeRange` iterates all primary span keys (`0x80` prefix) within the time range, sorted descending by start time.\n\n4. **Trace hydration**: the resulting trace ID list is resolved to full `*model.Trace` objects by prefix-scanning primary span records for each trace ID.\n\n---\n\n## Service and Operation Discovery\n\nService names and operation names are not queried directly from Badger on every request. Instead, they are maintained in an **in-memory cache** (`CacheStore`) that mirrors the TTL semantics of the underlying index entries:\n\n- `services: map[string]uint64` — maps service name to its expiry time (Unix seconds)\n- `operations: map[string]map[string]uint64` — maps service → operation → expiry time\n\nThe cache is populated in two ways:\n1. **On write**: `cache.Update(serviceName, operationName, expireTime)` is called after every `WriteSpan`, keeping the cache current without re-scanning Badger.\n2. **On startup** (if `prefillCache=true`): the reader scans all service name index keys (`0x81`) and operation name index keys (`0x82`) to preload any entries persisted from a previous run.\n\nExpired entries are lazily removed from the cache when `GetServices` or `GetOperations` is called.\n\n---\n\n## Storage Configuration\n\nKey operational parameters (from [`config.go`](../../internal/storage/v1/badger/config.go)):\n\n| Parameter              | Default          | Notes                                       |\n|------------------------|------------------|---------------------------------------------|\n| `TTL.Spans`            | 72 hours         | Expiry for all span-related Badger entries  |\n| `Ephemeral`            | `true`           | Uses a temp directory; data lost on restart |\n| `SyncWrites`           | `false`          | Async writes for performance                |\n| `MaintenanceInterval`  | 5 minutes        | Frequency of value-log GC runs              |\n| `MetricsUpdateInterval`| 10 seconds       | Frequency of metric collection              |\n| `Directories.Keys`     | `<exe>/data/keys`| Directory for LSM key index (SSD preferred) |\n| `Directories.Values`   | `<exe>/data/values` | Directory for value log (HDD acceptable) |\n\nThe separation of key and value directories allows placing the key index on faster SSD storage while the value log (which is written sequentially) can reside on slower spinning disk.\n\n---\n\n## Decision\n\nThe record layouts described above were chosen to satisfy the following requirements:\n\n1. **Lexicographic range scans**: all time-based queries rely on big-endian encoding of timestamps so that key iteration maps directly to time-range iteration.\n2. **Single-instance simplicity**: all record types share one Badger database, distinguished by prefix byte. This avoids the complexity of managing multiple database handles.\n3. **Index-only secondary records**: secondary index keys carry no values (nil), keeping the index footprint minimal. The full span data is always fetched from the primary record after the index identifies the relevant trace IDs.\n4. **TTL-driven expiry**: Badger's native per-entry TTL mechanism is used for automatic data expiration, eliminating the need for a background deletion job.\n5. **Embedded operation**: Badger requires no external process, making the Badger backend suitable for single-binary, all-in-one deployments.\n\n## Consequences\n\n### Positive\n\n- Simple deployment: no external storage infrastructure required.\n- Automatic expiry via native Badger TTL.\n- Efficient prefix and range scans over sorted keys.\n- Duration queries can be intersected with other query criteria (contrast with Cassandra; see [ADR-001](001-cassandra-find-traces-duration.md)).\n\n### Negative / Limitations\n\n- **Not distributed**: Badger is a single-node store. It is not suitable for high-throughput or multi-instance deployments.\n- **No spanKind in operations**: the operation name index does not encode span kind, so `GetOperations` returns operations without span kind information (tracked in [issue #1922](https://github.com/jaegertracing/jaeger/issues/1922)).\n- **String concatenation without separators**: the absence of separators between service name, tag key, and tag value in composite index keys means that a suffix of one component can collide with a prefix of the next. The implementation handles this with exact-prefix length checks but it is a latent source of subtle bugs if the key format is extended.\n- **No dependency index**: the dependency store computes dependency links via a full trace scan on every request rather than maintaining a dedicated index, which may be slow for large datasets.\n- **Sampling entries have no TTL**: throughput and probabilities records are not automatically expired and accumulate indefinitely unless explicitly pruned.\n- **Ephemeral by default**: the default configuration (`Ephemeral: true`) stores data in a temporary directory that is deleted on process exit, which may surprise users who expect data to persist across restarts.\n\n## References\n\n- [`internal/storage/v1/badger/spanstore/writer.go`](../../internal/storage/v1/badger/spanstore/writer.go) — `createTraceKV`, `createIndexKey`, `WriteSpan`\n- [`internal/storage/v1/badger/spanstore/reader.go`](../../internal/storage/v1/badger/spanstore/reader.go) — `FindTraceIDs`, `scanIndexKeys`, `scanRangeIndex`, `scanTimeRange`, `getTraces`\n- [`internal/storage/v1/badger/spanstore/cache.go`](../../internal/storage/v1/badger/spanstore/cache.go) — `CacheStore`\n- [`internal/storage/v1/badger/samplingstore/storage.go`](../../internal/storage/v1/badger/samplingstore/storage.go) — `createThroughputKV`, `createProbabilitiesKV`\n- [`internal/storage/v1/badger/config.go`](../../internal/storage/v1/badger/config.go) — `Config`, `DefaultConfig`\n- [Badger documentation](https://dgraph.io/docs/badger/)\n- [ADR-001: Cassandra FindTraceIDs Duration Query Behavior](001-cassandra-find-traces-duration.md)\n"
  },
  {
    "path": "docs/adr/006-internal-tracing-via-otelcol-telemetry-factory.md",
    "content": "# Internal Tracing via OTel Collector TelemetryFactory\n\n* **Status**: Implemented\n* **Date**: 2026-03-19\n\n## Context\n\nJaeger v2 is built as an OpenTelemetry Collector distribution. Like any well-instrumented service, it benefits from internal self-tracing: recording spans for query requests, MCP tool calls, and other extension-level operations. At the same time, self-tracing must not create recursive trace loops when Jaeger's own OTLP receiver is the export destination for internal telemetry.\n\nPreviously, two extensions (`jaegerquery` and `remotestorage`) each called `jtracer.NewProvider` manually at startup — a workaround for upstream Collector issue [#7532](https://github.com/open-telemetry/opentelemetry-collector/issues/7532), which is now closed. With the issue resolved, the Collector properly populates `component.TelemetrySettings.TracerProvider` for every component via `otelcol.Factories.Telemetry`.\n\n### Problem\n\nThree interlocking issues motivated this change:\n\n1. **Recursive self-tracing loop.** When Jaeger's OTLP receiver is the export destination for internal telemetry (the common deployment), each trace batch processed by the receiver generates an internal span that is exported as a new batch — ad infinitum.\n\n2. **Per-extension manual initialization.** Each extension that wanted self-tracing had to independently call `jtracer.NewProvider`, manage provider lifecycle, and override `telset.TracerProvider`. This is error-prone and bypasses the Collector framework's lifecycle management.\n\n3. **No per-component provider differentiation.** The standard `otelconftelemetry` factory creates one `TracerProvider` shared by all components. Upstream issue [#10663](https://github.com/open-telemetry/opentelemetry-collector/issues/10663), which would allow per-component customization, has had no progress.\n\n## Decision\n\nReplace the `otelconftelemetry.NewFactory()` call in `components.go` with a custom `WrapFactory` that delegates everything to `otelconftelemetry` except `CreateTracerProvider`.\n\nThe custom factory creates one real `TracerProvider` (via the existing `jtracer` initialization) wrapped in a `FilteringTracerProvider`. This wrapper inspects the `otelcol.component.id` instrumentation attribute that the Collector framework injects into every `Tracer()` call, and routes to the real provider only for an explicit allowlist of extensions known to produce meaningful internal spans:\n\n- `jaeger_query`\n- `jaeger_mcp`\n\nAll other components — receivers, processors, exporters, connectors, and unlisted extensions — receive a noop tracer. This default-off / explicit-allowlist policy closes the recursive loop by design and prevents uninstrumented components from accidentally emitting spans when they add internal instrumentation in the future.\n\nManual `jtracer.NewProvider` calls are removed from `jaegerquery/server.go` and `remotestorage/server.go`. Both extensions now use `telset.TracerProvider` directly, populated by the framework.\n\nThe `enable_tracing: false` config field in `jaeger_query` is preserved as a per-extension opt-out applied after the framework provides the provider.\n\n## Consequences\n\n- Recursive self-tracing loop is closed by design, not by documentation.\n- Extensions no longer manage tracer lifecycle; the Collector framework owns it.\n- New extensions get internal tracing by being added to the allowlist — no per-extension boilerplate.\n- The `otelcol.component.id` attribute injection is observed behavior from an internal Collector package, not a contractual API. An in-process test catches any upstream breakage at the next dependency bump.\n\n## Alternatives Considered\n\n**Use `otelconftelemetry` YAML tracing config as-is.** Requires users to add a `service.telemetry.traces` YAML block with an OTLP exporter config — a different and less familiar configuration paradigm compared to `OTEL_*` env vars. Does not solve the recursive loop by default (loop still occurs if the OTLP endpoint in the YAML points to Jaeger itself). Rejected.\n\n**Keep per-extension `jtracer.NewProvider`.** Does not solve the loop for receivers (which never called `jtracer.NewProvider`, so they always got the framework's noop). Does not benefit new extensions automatically. Rejected as a dead end.\n\n**Filter by instrumentation scope name prefix `go.opentelemetry.io/collector/receiver/`.** Fragile: depends on receiver authors following the naming convention, and would need updating as new receivers are added. Superseded by the component attribute approach.\n\n**Allowlist by `otelcol.component.kind = \"extension\"`, denylist receivers.** Allows all extensions through rather than only the two that have meaningful internal tracing today. Any future extension that adds instrumentation would emit spans without an explicit decision. Rejected in favour of the more conservative component-id allowlist.\n\n**Wait for upstream issue #10663.** No progress; no roadmap. Low confidence.\n"
  },
  {
    "path": "docs/adr/README.md",
    "content": "# Architecture Decision Records (ADRs)\n\nThis directory contains Architecture Decision Records (ADRs) for the Jaeger project. ADRs document important architectural decisions made during the development of Jaeger, including the context, decision, and consequences of each choice.\n\n## What is an ADR?\n\nAn Architecture Decision Record (ADR) is a document that captures an important architectural decision made along with its context and consequences. ADRs help teams understand why certain decisions were made and provide historical context for future contributors.\n\n## ADRs in This Repository\n\n- [ADR-001: Cassandra FindTraceIDs Duration Query Behavior](001-cassandra-find-traces-duration.md) - Explains why duration queries in the Cassandra spanstore use a separate code path and cannot be efficiently combined with other query parameters.\n- [ADR-002: MCP Server Extension](002-mcp-server.md) - Design for implementing Model Context Protocol server as a Jaeger extension for LLM integration.\n- [ADR-003: Lazy Storage Factory Initialization](003-lazy-storage-factory-initialization.md) - Comparative analysis of approaches to defer storage backend initialization until actually needed.\n- [ADR-004: Migrate Coverage Gating from Codecov to GitHub Actions](004-migrating-coverage-gating-to-github-actions.md) - Design for replacing Codecov PR gating with a local fan-in workflow that merges coverage profiles, gates on regression, and consolidates reporting with the existing metrics summary.\n- [ADR-005: Badger Storage Record Layouts](005-badger-storage-record-layouts.md) - Documents the key and value formats used to store spans, secondary indexes, and sampling data in the Badger embedded key-value store backend.\n- [ADR-006: Internal Tracing via OTel Collector TelemetryFactory](006-internal-tracing-via-otelcol-telemetry-factory.md) - Design for centralizing Jaeger's internal self-tracing through the Collector's TelemetryFactory hook, replacing per-extension manual tracer initialization and preventing recursive self-tracing loops in receivers.\n"
  },
  {
    "path": "docs/release/remove-v1-checklist.md",
    "content": "# Remove v1 release logic — incremental milestone checklist (updated)\n\nOwner: @yurishkuro  \nRelated: https://github.com/jaegertracing/jaeger/issues/7497  \nPrepared: 2025-11-12\n\n## Summary\n\nWe will perform a clean, audited migration from dual v1/v2 releases to v2-only releases. The migration is split into small, testable milestones so we do not break the ability to produce v1 artifacts until we intentionally stop publishing them.\n\nThis document is an update to the previously merged checklist and reflects the agreed milestone ordering and file allocations:\n\n- Milestone 0 — Coordination / snapshot (already done)\n- Milestone 1 — RE-NUMBER BUILD TARGETS TO USE v2 BY DEFAULT (build and image targets)\n- Milestone 2 — REMOVE ALL USAGE of v1 artifacts everywhere that could be invoked by maintainers or CI (non-breaking to release/publish)\n- Milestone 3 — STOP PUBLISHING v1 artifacts (release/publish changes)\n- Milestone 4 — Release notes & user-facing scripts (docs and helper finalization)\n- Milestone 5 — Cleanup remaining references (examples, tests, docs)\n- Milestone 6 — Final removal and prune (policy-based post-sunset)\n\nNotes:\n- \"Re-number build targets\" (Milestone 1) means change the defaults in build scripts and Makefiles so that most targets produce v2 artifacts by default, with explicit exceptions for selected v1 targets and the ability to override to v1 when needed.\n- \"Remove usage\" (Milestone 2) means update any convenience targets, examples, dev Makefiles, CI test helper scripts and READMEs that would cause contributors or CI to pick or run v1 artifacts by default. Do not change the core release/publish automation that we still need to be able to produce v1 artifacts until Milestone 3 (except where those core pieces are strictly only dev convenience and not needed for releases).\n- \"Stop publishing\" (Milestone 3) is the step where we change release automation so v1 artifacts are no longer produced/uploaded.\n\n---\n\n## Milestone 0 — Coordination (done)\n\n- Create a rollback snapshot branch/tag: `pre-remove-v1-YYYY-MM-DD`.\n- Baseline checklist merged: `docs/release/remove-v1-checklist.md`.\n\n---\n\n## Milestone 1 — RE-NUMBER BUILD TARGETS TO USE v2 BY DEFAULT\n\nOwner: @yurishkuro\n\nGoal\n- Ensure most build and image targets default to producing v2 artifacts. v1 should only be produced for the following targets (they remain v1):\n  - build-all-in-one\n  - build-query\n  - build-collector\n  - build-ingester\n- All other build targets (binaries and docker images) should default to v2. Maintain the ability to override to v1 via an explicit env var/Makefile variable (e.g., JAEGER_VERSION=1 or similar) but make v2 the default.\n\nAcceptance criteria\n- `scripts/makefiles/BuildBinaries.mk` and other Makefiles/targets produce v2 artifacts by default except for the explicit exceptions listed above.\n- Docker build scripts and helpers (examples: `scripts/build/build-upload-a-docker-image.sh`, docker-related Makefiles) default to v2 tags; v1 tag generation is only produced when explicitly requested.\n- CI or documented developer convenience targets no longer pull/build v1 artifacts by default.\n\nFiles / targets assigned to this milestone (non-exhaustive — guidance to scan repo)\n- [ ] `scripts/makefiles/BuildBinaries.mk`  \n  - Change defaults for targets other than the four exceptions listed above.\n- [ ] `scripts/build/build-upload-a-docker-image.sh`  \n  - Default to v2 push/tags.\n- [ ] `scripts/utils/compute-tags.sh`  \n  - Ensure default computed tags are v2-first.\n\nImplementation guidance\n- Make minimal edits: flip default variables so v2 is implied, leave an explicit override to v1.\n- Avoid changing core release/publishing automation that must still be able to publish v1 until the later milestone (this is M1 and non-publishing).\n- Apply same principle to Docker image builders and helpers.\n\nMilestone 1 testing\n- Run CI test jobs in staging and confirm builds do not produce or pull v1 artifacts by default.\n- Run Makefile targets and build scripts locally to validate v2 defaults and v1 override behaviour.\n\n---\n\n## Milestone 2 — REMOVE ALL USAGE of v1 artifacts (non-breaking to release/publish)\n\nGoal\n- Ensure no scripts, automated tests, documentation examples, or convenience targets that maintainers or CI use will pull, build, or reference v1 artifacts by default.\n- Do NOT change core release/publishing workflows that are required to produce v1 artifacts (those belong to Milestone 3).\n\nAcceptance criteria\n- CI test jobs & documented maintainer commands do not reference v1 by default.\n- Developer convenience targets and READMEs used in release/test flows are updated to v2 or removed.\n- Release/publish scripts remain able to produce v1 artifacts (unchanged in this milestone).\n\nFiles assigned to Milestone 2 (update usage only)\n- [ ] `docker-compose/tail-sampling/Makefile`  \n  - Replace `JAEGER_VERSION=1...` convenience defaults with v2 or remove v1 convenience targets.\n- [ ] `docker-compose/monitor/Makefile`  \n  - Update dev convenience targets and README examples to use v2 by default.\n- [ ] `examples/otel-demo/deploy-all.sh`  \n  - If the script is referenced by CI/docs, default to v2 (or make v1 explicit/legacy).\n- [ ] `examples/*` and README example lines that are invoked by CI or referenced in release docs  \n  - Update documented example commands to v2.\n- [ ] small convenience Makefile targets / scripts referenced in documentation or used by CI tests (identify by scan)  \n  - Replace v1 defaults with v2; remove legacy v1 targets where appropriate.\n- [ ] `scripts/e2e/*` (only test helpers invoked by CI, if they default to v1)  \n  - Update defaults used by CI test jobs to v2 (but do not modify release/publish scripts).\n- [ ] `scripts/utils/compare_metrics.py` (if used in tests or example automation)  \n  - Make v2 metrics the default for compare helpers invoked by CI.\n- [ ] Any other example/demo helpers that are used by CI or are part of the documented maintainer workflow (identify & update).\n\nImplementation guidance\n- Make minimal edits: change default literals, remove v1 convenience targets, update README example lines.\n- Avoid touching core release code paths (packaging, workflows that create upload actions, top-level make targets used by release automation).\n\nMilestone 2 testing\n- Run CI test jobs (staging) and ensure they don't pull v1 images by default.\n- Run example/demo commands from docs and confirm they use v2.\n- Sanity-check that release automation still can build v1 artifacts (no changes to release publish workflows in this milestone).\n\n---\n\n## Milestone 3 — STOP PUBLISHING v1 artifacts (release/publish changes)\n\nGoal\n- Change packaging and CI release automation so v1 artifacts are not built/pushed/uploaded for official releases.\n\nAcceptance criteria\n- Performing a release with a v2 tag (dry-run in a fork or staging) results in only v2 artifacts being published.\n- No v1 images/binaries are uploaded to registries or GitHub Releases.\n\nFiles assigned to Milestone 3 (publish removal)\n- [ ] `.github/workflows/ci-release.yml`  \n  - Remove steps that create/upload v1 release artifacts; ensure upload steps use v2 artifact names only.\n- [ ] `.github/workflows/ci-docker-build.yml` (publish-related steps)  \n  - Do not push v1 tags for official releases; push only v2.\n- [ ] `.github/workflows/ci-docker-hotrod.yml` (if it participates in release publish)  \n  - Ensure demo/image publishing uses v2 tags only.\n- [ ] `scripts/build/build-upload-a-docker-image.sh`  \n  - Remove v1 push logic and ensure push paths (for releases) only push v2 tags.\n- [ ] `scripts/build/package-deploy.sh`  \n  - Stop packaging/uploading `VERSION_V1` artifacts; upload only v2 artifacts. Remove checks that required both versions.\n- [ ] `scripts/utils/compute-tags.sh`  \n  - Ensure the computed publish tags for release flows are v2-only; remove v1 tag generation on release branch.\n- [ ] any other upload/publish helper invoked by the release workflow  \n  - Remove v1 publish behavior.\n\nImplementation guidance\n- These changes can safely alter the ability to publish v1 artifacts because we will have validated Milestone 1 and 2 first.\n- Keep changes explicit and reversible. Test on a fork/staging release.\n\nMilestone 3 testing\n- Run the CI release workflow on a fork with a v2 tag (dry-run) and verify only v2 artifacts are uploaded.\n- Verify Docker registry and GitHub Release contents.\n\n---\n\n## Milestone 4 — Release notes & user-facing scripts\n\nGoal\n- Update user-facing release docs and helper scripts so maintainers have a clean v2-only flow and instructions.\n\nFiles assigned to Milestone 4\n- [ ] `RELEASE.md`  \n  - Update instructions to be v2-only (replace \"tag v1 & v2\" with v2-only).\n- [ ] `CHANGELOG.md` (and any tools that parse its headers)  \n  - Ensure automated changelog tooling extracts v2 headers correctly; be tolerant of legacy format for a short transition time.\n- [ ] `scripts/release/start.sh`  \n  - Finalize prompts to v2-only (after Milestone 3).\n- [ ] `scripts/release/draft.py`  \n  - Draft v2-only GitHub releases; update headers and `gh release` invocations to use v2 tag.\n\nTesting\n- Run `start.sh -d` and `draft.py` in dry-run to validate v2-first outputs.\n- Validate maintainers can follow `RELEASE.md` to produce a v2 release.\n\n---\n\n## Milestone 5 — Cleanup remaining references (many small PRs)\n\nGoal\n- Sweep the repo and clean remaining `v1` references in examples, tests, CONTRIBUTING.md, and other non-critical areas. Split into small PRs.\n\nFiles / areas\n- [ ] `scripts/e2e/elasticsearch.sh` (finalize v2 default)\n- [ ] `scripts/utils/compare_metrics.py` (final cleanup)\n- [ ] `CONTRIBUTING.md` (document v2 as primary; note v1 status)\n- [ ] any remaining docker-compose examples, READMEs and sample scripts\n- [ ] any other files found by repo-wide `v1` sweep\n\nTesting\n- Run examples, e2e, and developer quickstarts; verify expected behavior.\n\n---\n\n## Milestone 6 — Final removal and prune (policy-based)\n\nGoal\n- After the sunset/support window ends, remove v1-only code, CI shards, docs and directories.\n\nAction\n- Delete v1-only directories and targets; remove legacy CI workflows and scripts.\n- Announce removal and update docs/website.\n\n---\n\n## PR strategy (recommended)\n\n- Keep PRs small and focused.\n  - PR A — Milestone 1: `chore/reassign-to-v2-defaults` — re-number build targets to use v2 by default (change Makefiles and build scripts). Include test plan: run CI builds, verify v2 artifacts are produced by default and v1 override works.\n  - PR B — Milestone 2: `chore/remove-v1-usage` — change convenience targets and examples (non-breaking). Include test plan: run CI tests, local smoke tests for example flows.\n  - PR C — Milestone 3: `chore/remove-v1-publish` — change release/publish workflows and packaging scripts. Test on fork with dry-run release.\n  - PR D — Milestone 4: docs & helper finalization.\n  - PR E+ — Milestone 5: many small PRs for examples/tests cleanup.\n- Each PR must include:\n  - short description of changes,\n  - explicit test plan (how to dry-run/validate),\n  - reviewer list (CI/release owners & @yurishkuro).\n\n---\n\n## QA & rollback\n\n- Always create a rollback snapshot branch before changing publishing logic: `pre-remove-v1-YYYY-MM-DD`.\n- For each PR:\n  - run CI tests in a fork/staging,\n  - run the release dry-run (for Milestone 3 PR),\n  - perform a quick sanity check of docs and examples.\n- If an urgent re-publish of v1 is required after removal, revert the Milestone 3 PR(s) and re-run the legacy snapshot branch to produce missing artifacts.\n\n---\n\n## Next actions\n\nPick one:\n- A) I will prepare a draft PR for **Milestone 1** (`chore/reassign-to-v2-defaults`) that re-numbers build targets to use v2 by default (change Makefiles and build scripts). (Recommended first step.)\n- B) I will prepare a draft PR for **Milestone 2** (`chore/remove-v1-usage`) that implements the minimal, safe changes to convenience Makefiles and example scripts and add a testing plan.\n- C) I will prepare patch diffs for review (no PRs).\n- D) You assign tasks to your team and I provide review guidance and diffs on demand.\n\nPlease confirm which path you prefer.\n"
  },
  {
    "path": "docs/security/architecture.md",
    "content": "# Jaeger Security Architecture\n\nThis document outlines the security architecture of Jaeger, focusing on cryptographic practices, input validation, and system hardening.\n\n## TLS and Cryptographic Practices\n\nJaeger supports TLS for all its network communications, including span ingestion, internal component communication, and access to the Query API and UI.\n\n### TLS Configuration\n\nTLS can be configured for both clients and servers across all Jaeger components (Collector, Query, Ingester, Agent).\n\n- **Supported TLS Versions**: Jaeger can be configured to use TLS 1.2 and 1.3, and these are the only versions that should be used in production. Users should configure the minimum supported version as TLS 1.2 or higher using the `--tls.min-version` flag (or corresponding YAML configuration), with TLS 1.3 recommended where available. While TLS 1.0 and 1.1 may still be technically supported for legacy interoperability, they are deprecated, have known security weaknesses, and **must not be enabled in production environments**.\n- **Cipher Suites**: A custom list of allowed cipher suites can be configured to ensure only strong cryptographic algorithms are used.\n- **Certificate Management**:\n    - **CA Certificate**: Can be provided to verify the server's or client's certificate.\n    - **Server Certificate and Key**: Required for enabling TLS on servers.\n    - **Client Authentication (mTLS)**: Jaeger supports mutual TLS, requiring clients to provide a valid certificate for authentication.\n- **Reloading Certificates**: Jaeger supports hot-reloading of TLS certificates and keys from the filesystem without restarting the service, controlled by a configurable reload interval.\n\n### Secure Defaults\n\n- **Certificate Verification**: When TLS is enabled, certificate verification is performed by default.\n- **Insecure Communication**: Users must explicitly set `insecure: true` or `insecure_skip_verify: true` to bypass security controls, which is strongly discouraged for production environments.\n\n## Input Validation\n\nJaeger performs strict input validation to prevent injection attacks and ensure system stability.\n\n- **OTLP and Protobuf**: Jaeger primarily uses structured data formats like OTLP (via gRPC and HTTP) and Protobuf for internal communication. These formats provide inherent protection against many common injection vulnerabilities.\n- **Schema Validation**: Inbound spans are validated against the defined schemas.\n- **Size Limits**:\n    - **gRPC Message Size**: Limits are enforced on the maximum size of incoming gRPC messages.\n    - **HTTP Request Size**: Limits are enforced for HTTP-based ingestion.\n- **Storage Queries**: Queries to storage backends (Elasticsearch, Cassandra, etc.) are constructed using parameterized queries or dedicated client libraries that prevent injection.\n\n## System Hardening\n\nJaeger is designed to be deployed in a hardened manner.\n\n### Container Security\n\n- **Minimal Base Images**: Jaeger's official Docker images are built using minimal base images like `alpine` or `scratch` to reduce the attack surface.\n- **Non-Root User**: Containers are designed to run as a non-privileged user where possible.\n\n### Dependency Management\n\n- **Vulnerability Scanning**: The project uses Dependabot for automated dependency monitoring and daily vulnerability scans.\n- **Software Bill of Materials (SBOM)**: An SBOM is generated for each release to provide transparency into the included components.\n\n### Secure Build Pipeline\n\n- **Signed Commits**: All contributions are required to follow the Developer Certificate of Origin (DCO) and should be signed.\n- **GitHub Actions Security**: The build pipeline uses security features like `harden-runner` to monitor and restrict network access during the build process.\n- **Release Signing**: All release artifacts and Git tags are GPG-signed by the maintainers.\n\n## Credential Management\n\n- **No Hardcoded Credentials**: Jaeger does not contain any hardcoded credentials. All secrets (passwords, tokens, etc.) must be provided via environment variables, command-line flags, or configuration files.\n- **Environment Variables**: Recommended for providing sensitive information in containerized environments.\n- **Secure Storage**: Users are encouraged to use secure secret management systems (like Kubernetes Secrets or HashiCorp Vault) to manage Jaeger's credentials.\n\n## References\n\n- [Securing Jaeger Installation](https://www.jaegertracing.io/docs/latest/security/)\n- [OpenTelemetry TLS Configuration](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configtls/README.md)\n"
  },
  {
    "path": "docs/security/assurance-case.md",
    "content": "# Jaeger Security Assurance Case\n\nThis document provides a security assurance case for the Jaeger project, demonstrating how security requirements are met through the application of secure design principles and mitigation of common implementation weaknesses.\n\n## Table of Contents\n\n- [Threat Model Summary](#threat-model-summary)\n- [Trust Boundaries](#trust-boundaries)\n- [Secure Design Principles](#secure-design-principles)\n- [Common Weakness Mitigations](#common-weakness-mitigations)\n- [Security Controls](#security-controls)\n\n## Threat Model Summary\n\nJaeger is a distributed tracing system that collects, stores, and visualizes trace data from instrumented applications. The primary security concerns are:\n\n1. **Data Confidentiality**: Trace data may contain sensitive information (service names, endpoints, timing data)\n2. **Data Integrity**: Trace data should not be tampered with\n3. **Availability**: The tracing infrastructure should not become a DoS vector\n4. **Access Control**: Only authorized users should access trace data\n\n### Threat Actors\n\n| Actor | Motivation | Capability |\n| -- | -- | -- |\n| Malicious Internal Service | DoS, data injection | Network access to collector |\n| External Attacker | Data exfiltration, reconnaissance | Varies based on deployment |\n| Unauthorized User | Access to sensitive traces | UI/API access |\n\nFor detailed threat analysis, see [threat-model.md](threat-model.md).\n\n## Trust Boundaries\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                    External Network                              │\n│  ┌──────────────┐                                               │\n│  │ Instrumented │                                               │\n│  │ Applications │ ─────────── BOUNDARY 1 ───────────────────┐   │\n│  │ (OTel SDK)   │                                           │   │\n│  └──────────────┘                                           ▼   │\n│                                                    ┌────────────┤\n│                                                    │  Jaeger    │\n│                                                    │  Collector │\n│                                                    └─────┬──────┤\n│                                                          │      │\n│                              ─────── BOUNDARY 2 ─────────┤      │\n│                                                          ▼      │\n│                                                    ┌────────────┤\n│                                                    │  Storage   │\n│                                                    │  Backend   │\n│                                                    └─────┬──────┤\n│                                                          │      │\n│                              ─────── BOUNDARY 3 ─────────┤      │\n│                                                          ▼      │\n│  ┌──────────────┐                                 ┌────────────┤\n│  │   Users      │ ─────────── BOUNDARY 4 ────────▶│   Jaeger   │\n│  │  (Browser)   │                                 │   Query/UI │\n│  └──────────────┘                                 └────────────┤\n└─────────────────────────────────────────────────────────────────┘\n```\n\n| Boundary | From | To | Security Controls |\n| -- | -- | -- | -- |\n| 1 | OTel SDK | Collector | TLS/mTLS, rate limiting |\n| 2 | Collector | Storage | TLS, authentication |\n| 3 | Storage | Query | TLS, authentication |\n| 4 | Users | Query/UI | TLS, bearer tokens, RBAC |\n\n## Secure Design Principles\n\n### Economy of Mechanism\n\n- **Implementation**: Jaeger leverages established protocols (OTLP, gRPC) rather than custom implementations\n- **Evidence**: Uses OpenTelemetry Collector framework for core functionality\n\n### Fail-Safe Defaults\n\n- **Implementation**: TLS certificate verification is enabled by default when TLS is configured\n- **Evidence**: `insecure_skip_verify` must be explicitly set to disable verification\n- **Note**: TLS itself is opt-in to simplify initial testing and non-production deployments; for all production deployments, TLS (preferably mTLS where supported) MUST be enabled on all external and inter-service connections.\n\n### Complete Mediation\n\n- **Implementation**: All API endpoints require passing through authentication when configured\n- **Evidence**: Bearer token and RBAC support at Query service level\n\n### Open Design\n\n- **Implementation**: All source code is publicly available on GitHub\n- **Evidence**: Apache 2.0 license, public security documentation\n\n### Separation of Privilege\n\n- **Implementation**: Different components (Collector, Query) can be deployed with different access levels\n- **Evidence**: Collector only writes, Query only reads from storage\n\n### Least Privilege\n\n- **Implementation**: Storage credentials can be scoped to minimum required permissions\n- **Evidence**: Separate read/write keyspaces supported for Cassandra\n\n### Least Common Mechanism\n\n- **Implementation**: Admin endpoints separated from data endpoints\n- **Evidence**: Separate ports for admin, metrics, and data APIs\n\n### Psychological Acceptability\n\n- **Implementation**: Security is configurable via standard YAML configuration\n- **Evidence**: Consistent TLS configuration across all components\n\n## Common Weakness Mitigations\n\n### OWASP Top 10 / CWE Top 25 Coverage\n\n| Weakness | Mitigation |\n| -- | -- |\n| **Injection (CWE-89, CWE-79)** | Structured data formats (protobuf/OTLP), parameterized storage queries |\n| **Broken Authentication (CWE-287)** | Bearer tokens, OAuth2, mTLS support |\n| **Sensitive Data Exposure (CWE-200)** | TLS for all communications, no credentials in traces |\n| **XML External Entities** | Not applicable - uses protobuf/JSON |\n| **Broken Access Control (CWE-284)** | RBAC support in Query service |\n| **Security Misconfiguration** | Secure defaults where possible, configuration validation |\n| **Cross-Site Scripting (CWE-79)** | UI built with React (auto-escaping), CSP headers |\n| **Insecure Deserialization (CWE-502)** | Uses protobuf with schema validation |\n| **Insufficient Logging** | Comprehensive logging in all components |\n| **SSRF (CWE-918)** | No user-controlled URLs in backend requests |\n\n### Go-Specific Security\n\n| Practice | Implementation |\n| -- | -- |\n| Memory Safety | Go's inherent memory safety |\n| Integer Overflow | Go's bounds checking |\n| Race Conditions | Go's race detector in CI |\n| Dependency Security | Dependabot, daily vulnerability scans |\n\n## Security Controls\n\n### Build and Release\n\n| Control | Implementation |\n| -- | -- |\n| Signed Commits | DCO required for all contributions |\n| Signed Releases | GPG-signed tags and artifacts |\n| SBOM | Generated for each release |\n| Container Security | Minimal base images (alpine/scratch) |\n| Supply Chain | Harden-Runner, pinned dependencies |\n\n### Runtime\n\n| Control | Implementation |\n| -- | -- |\n| TLS/mTLS | Configurable for all connections |\n| Authentication | Bearer tokens, OAuth2, Kerberos |\n| Rate Limiting | Configurable at collector |\n| Input Validation | OTLP schema validation, size limits |\n\n## References\n\n- [SECURITY.md](../../SECURITY.md) - Vulnerability reporting\n- [Threat Model](threat-model.md) - Detailed threat analysis\n- [Security Architecture](architecture.md) - Cryptographic practices\n- [Securing Jaeger Installation](https://www.jaegertracing.io/docs/latest/security/)\n"
  },
  {
    "path": "docs/security/self-assessment.md",
    "content": "# Jaeger Self-Assessment\n\nThis document is a local copy of the Jaeger project's security self-assessment, originally conducted following the CNCF TAG Security assessment process.\n\n## Project Overview\n\nJaeger is a distributed tracing system originally developed at Uber Technologies and now a graduated project within the Cloud Native Computing Foundation (CNCF).\n\n## Security Profile\n\n| Attribute | Value |\n| -- | -- |\n| Security Policy | [SECURITY.md](https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md) |\n| Threat Model | [threat-model.md](threat-model.md) |\n| Assurance Case | [assurance-case.md](assurance-case.md) |\n| Security file | [SECURITY.md](https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md) |\n\n## Self-Assessment Summary\n\n### Secure Design Principles\n\nJaeger adheres to established secure design principles:\n- **Economy of Mechanism**: Uses standard protocols (OTLP, gRPC).\n- **Fail-Safe Defaults**: TLS verification enabled by default.\n- **Open Design**: Fully open-source and publicly documented.\n\n### Trust Boundaries\n\nTrust boundaries exist between instrumented applications and the collector, between the collector and storage, and between the query service and users. Each boundary is protected by TLS and authentication controls.\n\n### Security Testing\n\n- **Unit/Integration Tests**: Comprehensive test suite with high coverage requirements.\n- **Static Analysis**: Uses `golangci-lint` and `gosec`.\n- **Dependency Scanning**: Daily scans via Dependabot.\n- **Vulnerability Reporting**: Formal process documented in `SECURITY.md`.\n\n## Metadata\n\n| Attribute | Details |\n| -- | -- |\n| Last Updated | 2026-01-16 |\n| Status | Completed |\n| Assessment Process | CNCF TAG Security Self-Assessment |\n\n## Vulnerability Handling\n\nRefer to [SECURITY.md](https://github.com/jaegertracing/jaeger/blob/main/SECURITY.md) and [Report Security Issue](https://www.jaegertracing.io/report-security-issue/).\n"
  },
  {
    "path": "docs/security/threat-model.md",
    "content": "# Jaeger Threat Model\n\nThis document describes the threat model for the Jaeger distributed tracing system.\n\n## Overview\n\nJaeger is a distributed tracing platform that collects, processes, and visualizes trace data from instrumented applications. This threat model identifies potential threats and the controls implemented to mitigate them.\n\n## System Architecture\n\n```\n┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐\n│  Instrumented   │      │     Jaeger      │      │    Storage      │\n│  Applications   │─────▶│    Collector    │─────▶│    Backend      │\n│  (OTel SDK)     │      │                 │      │ (ES/Cassandra)  │\n└─────────────────┘      └─────────────────┘      └────────┬────────┘\n                                                           │\n                         ┌─────────────────┐               │\n                         │     Jaeger      │◀──────────────┘\n                         │   Query + UI    │\n                         └────────┬────────┘\n                                  │\n                         ┌────────▼────────┐\n                         │      Users      │\n                         │    (Browser)    │\n                         └─────────────────┘\n```\n\n## Trust Boundaries\n\n| Boundary | Description | Security Controls |\n| -- | -- | -- |\n| **B1: SDK → Collector** | External applications sending spans | TLS/mTLS, rate limiting, schema validation |\n| **B2: Collector → Storage** | Internal service to database | TLS, authentication, authorized credentials |\n| **B3: Storage → Query** | Database to internal service | TLS, authentication, read-only access |\n| **B4: Query → Users** | Internal service to end users | TLS, bearer tokens, RBAC |\n\n## Threat Actors\n\n| Actor | Description | Motivation |\n| -- | -- | -- |\n| **Malicious Application** | Compromised or rogue service sending traces | Data poisoning, DoS, information injection |\n| **External Attacker** | Attacker with network access | Data exfiltration, reconnaissance, DoS |\n| **Malicious Insider** | User with legitimate access | Unauthorized data access, privilege escalation |\n| **Man-in-the-Middle** | Attacker on network path | Data interception, tampering |\n\n## Threats and Mitigations\n\n### T1: Denial of Service via Span Flooding\n\n**Description**: Malicious or misconfigured application sends excessive spans.\n\n| Attribute | Value |\n| -- | -- |\n| Threat Actor | Malicious Application |\n| Impact | High - Can overwhelm collector and storage |\n| Likelihood | Medium |\n\n**Mitigations**:\n- Rate limiting at collector\n- Adaptive sampling to reduce volume\n- Resource quotas per service\n- Kafka buffering for burst handling\n\n### T2: Sensitive Data Exposure in Traces\n\n**Description**: Traces may inadvertently contain sensitive data (PII, credentials).\n\n| Attribute | Value |\n| -- | -- |\n| Threat Actor | External Attacker, Malicious Insider |\n| Impact | High - Data breach |\n| Likelihood | Medium |\n\n**Mitigations**:\n- TLS encryption for all connections\n- Access control (RBAC) on Query service\n- Data retention policies\n- Guidance for users on what not to trace\n\n### T3: Man-in-the-Middle Attack\n\n**Description**: Attacker intercepts unencrypted trace traffic.\n\n| Attribute | Value |\n| -- | -- |\n| Threat Actor | Man-in-the-Middle |\n| Impact | High - Data interception and tampering |\n| Likelihood | Low (with TLS) |\n\n**Mitigations**:\n- TLS/mTLS for all communications\n- Certificate verification enabled by default\n- Certificate pinning optional\n\n### T4: Unauthorized Access to Trace Data\n\n**Description**: Unauthorized user accesses the Query UI/API.\n\n| Attribute | Value |\n| -- | -- |\n| Threat Actor | External Attacker, Malicious Insider |\n| Impact | Medium - Information disclosure |\n| Likelihood | Medium |\n\n**Mitigations**:\n- Bearer token authentication\n- OAuth2 integration\n- RBAC for access control\n- Audit logging\n\n### T5: Storage Backend Compromise\n\n**Description**: Attacker gains access to the storage backend directly.\n\n| Attribute | Value |\n| -- | -- |\n| Threat Actor | External Attacker |\n| Impact | High - Full data access |\n| Likelihood | Low |\n\n**Mitigations**:\n- Storage-level authentication\n- Network isolation\n- Encrypted connections to storage\n- Storage-level access controls\n\n### T6: Supply Chain Attack\n\n**Description**: Compromised dependency introduced into build.\n\n| Attribute | Value |\n| -- | -- |\n| Threat Actor | External Attacker |\n| Impact | Critical - Code execution |\n| Likelihood | Low |\n\n**Mitigations**:\n- Dependabot vulnerability scanning\n- Signed commits (DCO)\n- GPG-signed releases\n- SBOM generation\n- Pinned dependencies with checksums\n\n## Security Recommendations\n\n### For Operators\n\n1. **Enable TLS everywhere** - Use `tls.insecure: false` for all connections\n2. **Use mTLS** - Especially for collector ingestion\n3. **Configure authentication** - Enable bearer tokens or OAuth2\n4. **Set up RBAC** - Limit who can access trace data\n5. **Enable audit logging** - Track access to sensitive traces\n6. **Use network segmentation** - Isolate Jaeger components\n\n### For Developers Instrumenting Applications\n\n1. **Never trace credentials** - Avoid logging passwords, tokens, API keys\n2. **Sanitize PII** - Don't include personal information in spans\n3. **Use sampling** - Reduce volume and exposure\n4. **Review span content** - Audit what data is being traced\n\n## References\n\n- [SECURITY.md](../../SECURITY.md) - Security policy and vulnerability reporting\n- [Security Architecture](architecture.md) - Cryptographic practices\n- [Assurance Case](assurance-case.md) - Security assurance case\n- [Securing Jaeger Installation](https://www.jaegertracing.io/docs/latest/security/)\n- [OpenSSF Threat Modeling Standards](https://github.com/ossf/security-insights-spec/tree/main/docs/threat-model)\n"
  },
  {
    "path": "docs/security/verifying-releases.md",
    "content": "# Verifying Jaeger Releases\n\nAll Jaeger releases are cryptographically signed. Users should verify signatures before using release artifacts to ensure they have not been tampered with.\n\n## Signed Artifacts\n\n| Artifact Type | Signing Method |\n|---------------|----------------|\n| Git tags | GPG signed (`git tag -s`) |\n| Binary archives | GPG detached signatures (`.asc` files) |\n| Container images | Verify image digest from official Docker Hub and Quay.io repositories |\n| SBOM | Included with each release |\n\n## Verifying Container Image Authenticity\n\nJaeger container images are published to official repositories on Docker Hub and Quay.io. To verify that you are using the intended image:\n\n1. Pull images from the official Jaeger organization repositories on Docker Hub or Quay.io.\n2. Use image digests (for example, `jaegertracing/all-in-one@sha256:<digest>`) rather than mutable tags where possible.\n3. Compare the digest you deploy with the expected digest published in your deployment configuration, automation, or release notes.\n\n## Verifying Binary Signatures\n\n1. **Import the Jaeger GPG public key**:\n   The Jaeger public key (`C043A4D2B3F2AC31`) is available on all major key servers. See [SECURITY.md](../../SECURITY.md#our-public-key) for the full key block.\n\n   ```bash\n   gpg --keyserver keyserver.ubuntu.com --recv-keys C043A4D2B3F2AC31\n   ```\n\n2. **Download the release artifact and its signature**:\n   ```bash\n   # Example for version v1.55.0\n   wget https://github.com/jaegertracing/jaeger/releases/download/v1.55.0/jaeger-1.55.0-linux-amd64.tar.gz\n   wget https://github.com/jaegertracing/jaeger/releases/download/v1.55.0/jaeger-1.55.0-linux-amd64.tar.gz.asc\n   ```\n\n3. **Verify the signature**:\n   ```bash\n   gpg --verify jaeger-1.55.0-linux-amd64.tar.gz.asc jaeger-1.55.0-linux-amd64.tar.gz\n   ```\n\n## Verifying Git Tag Signatures\n\nYou can verify the signature of any Jaeger Git tag using the following commands:\n\n```bash\ngit fetch --tags\ngit tag -v v1.55.0\n```\n"
  },
  {
    "path": "empty_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestDummy(*testing.T) {\n\t// This is a dummy test in the root package.\n\t// Without it `go test -v .` prints \"testing: warning: no tests to run\".\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/grafana-integration/README.md",
    "content": "# Hot R.O.D. - Rides on Demand  - Grafana integration\n\nThis example combines the Hot R.O.D. demo application ([examples/hotrod/](../hotrod/)) with Grafana, Loki and Prometheus integration, to demonstrate logs, metrics and traces correlation.\n\n## Running via `docker compose`\n\n### Prerequisites\n\n* Clone the Jaeger repository `git clone https://github.com/jaegertracing/jaeger.git`, then `cd examples/grafana-integration`\n\n* All services will log to Loki via the [official Docker driver plugin](https://grafana.com/docs/loki/latest/clients/docker-driver/).\nInstall the Loki logging plugin for Docker:\n\n```bash\ndocker plugin install \\\ngrafana/loki-docker-driver:latest \\\n--alias loki \\\n--grant-all-permissions\n```\n\n### Run the services\n\n`docker compose up`\n\n### Access the services\n* HotROD application at http://localhost:8080\n* Grafana UI at http://localhost:3000 (default credentials: admin/admin)\n\n### Explore with Loki\n\nIt is possible to correlate application logs with traces via Grafana's Explore interface.\n\nAfter setting the datasource to Loki, all the log labels become available, and can be easily filtered using [Loki's LogQL query language](https://grafana.com/docs/loki/latest/logql/).\n\nFor example, after selecting the compose project/service under Log labels , errors can be filtered with the following expression:\n\n```\n{compose_project=\"grafana-integration\"} |= \"error\"\n```\n\nwhich will list the redis timeout events.\n\n### HotROD - Metrics and logs overview dashboard\n\nSince the HotROD application can expose its metrics in Prometheus' format, these can be also used during investigation.\n\nThis example includes a dashboard that contains a log panel for the selected services in real time. These can be also filtered by a search field, that provides `grep`-like features.\n\nThere are also panels to display the ratio/percentage of errors in the current timeframe.\n\nAdditionally, there are graphs for each service, visualizing the rate of the requests and showing latency percentiles.\n\n### Clean up\n\n`docker compose down`\n"
  },
  {
    "path": "examples/grafana-integration/docker-compose.yaml",
    "content": "services:\n  grafana:\n    image: grafana/grafana:10.2.2\n    ports:\n      - '3000:3000'\n    volumes:\n      - ./grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml\n      - ./grafana/dashboard.yml:/etc/grafana/provisioning/dashboards/dashboard.yml\n      - ./grafana/hotrod_metrics_logs.json:/etc/grafana/provisioning/dashboards/hotrod_metrics_logs.json\n    logging:\n      driver: loki\n      options:\n        loki-url: 'http://localhost:3100/api/prom/push'\n\n  loki:\n    image: grafana/loki:2.9.2\n    ports:\n      - '3100:3100'\n    command: -config.file=/etc/loki/local-config.yaml\n    # send Loki traces to Jaeger\n    environment:\n      - JAEGER_AGENT_HOST=jaeger\n      - JAEGER_AGENT_PORT=6831\n      - JAEGER_SAMPLER_TYPE=const\n      - JAEGER_SAMPLER_PARAM=1\n    logging:\n      driver: loki\n      options:\n        loki-url: 'http://localhost:3100/api/prom/push'\n        # Prevent container from being stuck when shutting down\n        # https://github.com/grafana/loki/issues/2361#issuecomment-718024318\n        loki-timeout: 1s\n        loki-max-backoff: 1s\n        loki-retries: 1\n\n  jaeger:\n    image: cr.jaegertracing.io/jaegertracing/all-in-one:1.51\n    ports:\n      - '6831:6831'\n      - '16686:16686'\n      - '4318:4318'\n    logging:\n      driver: loki\n      options:\n        loki-url: 'http://localhost:3100/api/prom/push'\n\n  hotrod:\n    image: cr.jaegertracing.io/jaegertracing/example-hotrod:1.51\n    depends_on:\n      - jaeger\n    ports:\n      - '8080:8080'\n      - '8083:8083'\n    command: [\"-m\",\"prometheus\",\"all\"]\n    environment:\n      - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318\n    logging:\n      driver: loki\n      options:\n        loki-url: 'http://localhost:3100/api/prom/push'\n\n  prometheus:\n    image: prom/prometheus:v2.48.0\n    volumes:\n      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro\n    ports:\n      - '9090:9090'\n    command:\n      - --config.file=/etc/prometheus/prometheus.yml\n    logging:\n      driver: loki\n      options:\n        loki-url: 'http://localhost:3100/api/prom/push'\n"
  },
  {
    "path": "examples/grafana-integration/grafana/dashboard.yml",
    "content": "apiVersion: 1\n\nproviders:\n- name: 'HotROD'\n  orgId: 1\n  folder: ''\n  type: file\n  disableDeletion: false\n  editable: true\n  options:\n    path: /etc/grafana/provisioning/dashboards\n"
  },
  {
    "path": "examples/grafana-integration/grafana/datasources.yaml",
    "content": "apiVersion: 1\n\ndatasources:\n  - name: Loki\n    type: loki\n    access: proxy\n    url: http://loki:3100\n    editable: true\n    isDefault: true\n  - name: Jaeger\n    type: jaeger\n    access: browser\n    url: http://jaeger:16686\n    editable: true\n  - name: Prometheus\n    type: prometheus\n    access: proxy\n    url: http://prometheus:9090\n"
  },
  {
    "path": "examples/grafana-integration/grafana/hotrod_metrics_logs.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\",\n          \"uid\": \"grafana\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"description\": \"\",\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"gnetId\": 12611,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"datasource\": {\n        \"type\": \"loki\",\n        \"uid\": \"P8E80F9AEF21F6940\"\n      },\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 43,\n      \"panels\": [],\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"loki\",\n            \"uid\": \"P8E80F9AEF21F6940\"\n          },\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Loki Overview\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"loki\",\n        \"uid\": \"P8E80F9AEF21F6940\"\n      },\n      \"description\": \"Total  Count of log lines in the specified time range\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"color\": \"rgb(31, 255, 7)\",\n                  \"text\": \"0\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgb(31, 255, 7)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgb(31, 255, 7)\",\n                \"value\": 10\n              },\n              {\n                \"color\": \"rgb(31, 255, 7)\",\n                \"value\": 50\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 11,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"sum\"\n          ],\n          \"fields\": \"/^{}$/\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\",\n        \"wideLayout\": true\n      },\n      \"pluginVersion\": \"10.2.2\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"loki\",\n            \"uid\": \"P8E80F9AEF21F6940\"\n          },\n          \"expr\": \"sum(count_over_time(({container_name=\\\"$container_name\\\"})[$__interval]))\",\n          \"hide\": false,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Total  Count of logs\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"loki\",\n        \"uid\": \"P8E80F9AEF21F6940\"\n      },\n      \"description\": \"Total Count: of pattern: $searchable_pattern in the specified time range\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"color\": \"rgb(222, 15, 43)\",\n                  \"text\": \"0\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgb(222, 15, 43)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgb(222, 15, 43)\",\n                \"value\": 10\n              },\n              {\n                \"color\": \"rgb(222, 15, 43)\",\n                \"value\": 50\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"id\": 6,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"sum\"\n          ],\n          \"fields\": \"/^{}$/\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\",\n        \"wideLayout\": true\n      },\n      \"pluginVersion\": \"10.2.2\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"loki\",\n            \"uid\": \"P8E80F9AEF21F6940\"\n          },\n          \"expr\": \"sum(count_over_time(({container_name=\\\"$container_name\\\"} |~ \\\"(?i)$searchable_pattern\\\")[$__interval]))\",\n          \"hide\": false,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Total Count: of pattern $searchable_pattern\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"loki\",\n        \"uid\": \"P8E80F9AEF21F6940\"\n      },\n      \"description\": \"Live logs is a like 'tail -f' in a real time\",\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 18,\n        \"x\": 0,\n        \"y\": 4\n      },\n      \"id\": 2,\n      \"options\": {\n        \"dedupStrategy\": \"none\",\n        \"enableLogDetails\": true,\n        \"prettifyLogMessage\": false,\n        \"showCommonLabels\": false,\n        \"showLabels\": false,\n        \"showTime\": false,\n        \"sortOrder\": \"Descending\",\n        \"wrapLogMessage\": false\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"loki\",\n            \"uid\": \"P8E80F9AEF21F6940\"\n          },\n          \"expr\": \"{container_name=\\\"$container_name\\\"} |~ \\\"(?i)$searchable_pattern\\\" \",\n          \"hide\": false,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Live logs\",\n      \"type\": \"logs\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"loki\",\n        \"uid\": \"P8E80F9AEF21F6940\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"color\": \"#299c46\",\n                  \"text\": \"0\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"max\": 100,\n          \"min\": 0,\n          \"noValue\": \"0\",\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 10\n              },\n              {\n                \"color\": \"#C4162A\",\n                \"value\": 50\n              }\n            ]\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 4\n      },\n      \"id\": 9,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"minVizHeight\": 75,\n        \"minVizWidth\": 75,\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": false\n      },\n      \"pluginVersion\": \"10.2.2\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"loki\",\n            \"uid\": \"P8E80F9AEF21F6940\"\n          },\n          \"expr\": \"sum(count_over_time(({container_name=\\\"$container_name\\\"} |~ \\\"(?i)$searchable_pattern\\\")[$__interval])) * 100 / sum(count_over_time(({container_name=\\\"$container_name\\\"})[$__interval]))\",\n          \"hide\": false,\n          \"legendFormat\": \"%\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Percentage of \\\"$searchable_pattern\\\"  for specified time\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": {\n        \"type\": \"loki\",\n        \"uid\": \"P8E80F9AEF21F6940\"\n      },\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 12\n      },\n      \"id\": 37,\n      \"panels\": [],\n      \"repeat\": \"endpoint\",\n      \"repeatDirection\": \"h\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"loki\",\n            \"uid\": \"P8E80F9AEF21F6940\"\n          },\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Endpoint: $endpoint\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"PBFA97CFB590B2093\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 7,\n        \"x\": 0,\n        \"y\": 13\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"10.2.2\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"PBFA97CFB590B2093\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(hotrod_http_requests_total{endpoint=~\\\"$endpoint\\\"}[5m])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{status_code}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Frontend HTTP response codes\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"PBFA97CFB590B2093\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 1,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 7,\n        \"y\": 13\n      },\n      \"id\": 25,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"10.2.2\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"PBFA97CFB590B2093\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(rate(hotrod_http_requests_total{endpoint=\\\"/\\\"}[5m])) by (error)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{endpoint}} error: {{error}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Frontend requests total\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"PBFA97CFB590B2093\"\n      },\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 9,\n        \"x\": 15,\n        \"y\": 13\n      },\n      \"id\": 15,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"multi\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"10.2.2\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"PBFA97CFB590B2093\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.99, sum(rate(hotrod_request_latency_bucket{endpoint=\\\"/\\\"}[5m])) by (le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P99\",\n          \"range\": true,\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"PBFA97CFB590B2093\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.95, sum(rate(hotrod_request_latency_bucket{endpoint=\\\"/\\\"}[5m])) by (le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P95\",\n          \"range\": true,\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"PBFA97CFB590B2093\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"histogram_quantile(0.50, sum(rate(hotrod_request_latency_bucket{endpoint=\\\"/\\\"}[5m])) by (le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50\",\n          \"range\": true,\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Frontend latency percentiles\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"10s\",\n  \"schemaVersion\": 38,\n  \"tags\": [\n    \"Loki\",\n    \"logging\",\n    \"prometheus\",\n    \"metrics\",\n    \"jaeger\",\n    \"hotrod\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"grafana-integration-hotrod-1\",\n          \"value\": \"grafana-integration-hotrod-1\"\n        },\n        \"datasource\": {\n          \"type\": \"loki\",\n          \"uid\": \"P8E80F9AEF21F6940\"\n        },\n        \"definition\": \"label_values({container_name=~\\\".+\\\"}, container_name)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Service\",\n        \"multi\": false,\n        \"name\": \"container_name\",\n        \"options\": [],\n        \"query\": \"label_values({container_name=~\\\".+\\\"}, container_name)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"error\",\n          \"value\": \"error\"\n        },\n        \"hide\": 0,\n        \"label\": \"Search (case insensitive)\",\n        \"name\": \"searchable_pattern\",\n        \"options\": [\n          {\n            \"selected\": true,\n            \"text\": \"error\",\n            \"value\": \"error\"\n          }\n        ],\n        \"query\": \"error\",\n        \"skipUrlSync\": false,\n        \"type\": \"textbox\"\n      },\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": [\n            \"All\"\n          ],\n          \"value\": [\n            \"$__all\"\n          ]\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"PBFA97CFB590B2093\"\n        },\n        \"definition\": \"label_values(hotrod_requests_total,endpoint)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": \"Service\",\n        \"multi\": true,\n        \"name\": \"endpoint\",\n        \"options\": [],\n        \"query\": {\n          \"qryType\": 1,\n          \"query\": \"label_values(hotrod_requests_total,endpoint)\",\n          \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"HotROD - Metrics and Logs Overview\",\n  \"uid\": \"fRIvzUZMz\",\n  \"version\": 1,\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "examples/grafana-integration/prometheus/prometheus.yml",
    "content": "global:\n  scrape_interval:     15s\n  evaluation_interval: 15s\n\nscrape_configs:\n  - job_name: 'prometheus'\n    scrape_interval: 5s\n    static_configs:\n         - targets:\n           - localhost:9090\n\n  - job_name: 'hotrod-application'\n    static_configs:\n         - targets:\n           - hotrod:8080\n"
  },
  {
    "path": "examples/hotrod/.gitignore",
    "content": "hotrod-linux\n"
  },
  {
    "path": "examples/hotrod/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nFROM alpine:3.23.0@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 AS cert\nRUN apk add --update --no-cache ca-certificates\n\nFROM scratch\nARG TARGETARCH\nEXPOSE 8080 8081 8082 8083\nCOPY --from=cert /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\nCOPY hotrod-linux-$TARGETARCH /go/bin/hotrod-linux\nENTRYPOINT [\"/go/bin/hotrod-linux\"]\nCMD [\"all\"]\n\n"
  },
  {
    "path": "examples/hotrod/README.md",
    "content": "# Hot R.O.D. - Rides on Demand\n\nThis is a demo application that consists of several microservices and illustrates the use of the OpenTelemetry API & SDK. It can be run standalone, but requires Jaeger backend to view the traces. A tutorial / walkthrough is available:\n  * as a blog post [Take Jaeger for a HotROD ride][hotrod-tutorial],\n  * as a video [OpenShift Commons Briefing: Distributed Tracing with Jaeger & Prometheus on Kubernetes][hotrod-openshift].\n\nAs of Jaeger v1.42.0 this application was upgraded to use the OpenTelemetry SDK for traces.\n\n## Features\n\n* Discover architecture of the whole system via data-driven dependency diagram\n* View request timeline & errors, understand how the app works\n* Find sources of latency, lack of concurrency\n* Highly contextualized logging\n* Use baggage propagation to\n  * Diagnose inter-request contention (queueing)\n  * Attribute time spent in a service\n* Use [opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib) open source libraries to instrument HTTP and gRPC requests with minimal code changes\n\n## Running\n\n💥💥💥 Try it with Jaeger v2!  See [cmd/jaeger](../../cmd/jaeger).\n\n### Run everything via `docker compose`\n\n* Download `docker-compose.yml` from https://github.com/jaegertracing/jaeger/blob/main/examples/hotrod/docker-compose.yml\n* Optional: find the latest Jaeger version (see https://www.jaegertracing.io/download/) and pass it via environment variable `JAEGER_VERSION`. Otherwise `docker compose` will use the `latest` tag, which is fine for the first time you download the images, but once they are in your local registry the `latest` tag is never updated and you may be running stale (and possibly incompatible) verions of Jaeger and the HotROD app.\n* Run Jaeger backend and HotROD demo, e.g.:\n  * `JAEGER_VERSION=2.14.0 docker compose -f path-to-yml-file up`\n* Access Jaeger UI at http://localhost:16686 and HotROD app at http://localhost:8080\n* Shutdown / cleanup with `docker compose -f path-to-yml-file down`\n\nAlternatively, you can run each component separately as described below.\n\n### Run everything in Kubernetes\n\n```bash\nkustomize build ./kubernetes | kubectl apply -f -\nkubectl port-forward -n example-hotrod service/example-hotrod 8080:frontend\n# In another terminal\nkubectl port-forward -n example-hotrod service/jaeger 16686:frontend\n\n# To cleanup\nkustomize build ./kubernetes | kubectl delete -f -\n```\n\nAccess Jaeger UI at http://localhost:16686 and HotROD app at http://localhost:8080\n\n### Run Jaeger backend\n\nJaeger backend is packaged as a Docker container with in-memory storage.\n\n```bash\ndocker run \\\n  --rm \\\n  --name jaeger \\\n  -p4318:4318 \\\n  -p16686:16686 \\\n  -p14268:14268 \\\n  jaegertracing/jaeger:latest\n```\n\nJaeger UI can be accessed at http://localhost:16686.\n\n### Run HotROD from source\n\n```bash\ngit clone git@github.com:jaegertracing/jaeger.git jaeger\ncd jaeger\ngo run ./examples/hotrod/main.go all\n```\n\n### Run HotROD from docker\n```bash\ndocker run \\\n  --rm \\\n  --link jaeger \\\n  --env OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 \\\n  -p8080-8083:8080-8083 \\\n  jaegertracing/example-hotrod:latest \\\n  all\n```\n\nThen open http://127.0.0.1:8080\n\n## Metrics\n\nThe app exposes metrics in either Go's `expvar` format (by default) or in Prometheus format (enabled via `-m prometheus` flag).\n  * `expvar`: `curl http://127.0.0.1:8080/debug/vars`\n  * Prometheus: `curl http://127.0.0.1:8080/metrics`\n\n## Linking to traces\n\nThe HotROD UI can generate links to the Jaeger UI to find traces corresponding\nto each executed request. By default it uses the standard Jaeger UI address\nhttp://localhost:16686, but if your Jaeger UI is running at a different address,\nit can be customized via `-j <address>` flag passed to HotROD, e.g.\n\n```\ngo run ./examples/hotrod/main.go all -j http://jaeger-ui:16686\n```\n\n[hotrod-tutorial]: https://medium.com/jaegertracing/take-jaeger-for-a-hotrod-ride-233cf43e46c2\n[hotrod-openshift]: https://blog.openshift.com/openshift-commons-briefing-82-distributed-tracing-with-jaeger-prometheus-on-kubernetes/\n"
  },
  {
    "path": "examples/hotrod/cmd/all.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\n// allCmd represents the all command\nvar allCmd = &cobra.Command{\n\tUse:   \"all\",\n\tShort: \"Starts all services\",\n\tLong:  `Starts all services.`,\n\tRunE: func(_ *cobra.Command, args []string) error {\n\t\tlogger.Info(\"Starting all services\")\n\t\tgo customerCmd.RunE(customerCmd, args)\n\t\tgo driverCmd.RunE(driverCmd, args)\n\t\tgo routeCmd.RunE(routeCmd, args)\n\t\treturn frontendCmd.RunE(frontendCmd, args)\n\t},\n}\n\nfunc init() {\n\tRootCmd.AddCommand(allCmd)\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/customer.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/customer\"\n)\n\n// customerCmd represents the customer command\nvar customerCmd = &cobra.Command{\n\tUse:   \"customer\",\n\tShort: \"Starts Customer service\",\n\tLong:  `Starts Customer service.`,\n\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\tzapLogger := logger.With(zap.String(\"service\", \"customer\"))\n\t\tlogger := log.NewFactory(zapLogger)\n\t\tserver := customer.NewServer(\n\t\t\tnet.JoinHostPort(customerHostname, strconv.Itoa(customerPort)),\n\t\t\totelExporter,\n\t\t\tmetricsFactory,\n\t\t\tlogger,\n\t\t)\n\t\treturn logError(zapLogger, server.Run())\n\t},\n}\n\nfunc init() {\n\tRootCmd.AddCommand(customerCmd)\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/driver.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/driver\"\n)\n\n// driverCmd represents the driver command\nvar driverCmd = &cobra.Command{\n\tUse:   \"driver\",\n\tShort: \"Starts Driver service\",\n\tLong:  `Starts Driver service.`,\n\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\tzapLogger := logger.With(zap.String(\"service\", \"driver\"))\n\t\tlogger := log.NewFactory(zapLogger)\n\t\tserver := driver.NewServer(\n\t\t\tnet.JoinHostPort(driverHostname, strconv.Itoa(driverPort)),\n\t\t\totelExporter,\n\t\t\tmetricsFactory,\n\t\t\tlogger,\n\t\t)\n\t\treturn logError(zapLogger, server.Run())\n\t},\n}\n\nfunc init() {\n\tRootCmd.AddCommand(driverCmd)\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/flags.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\totelExporter string // otlp, stdout\n\tverbose      bool\n\n\tfixDBConnDelay         time.Duration\n\tfixDBConnDisableMutex  bool\n\tfixRouteWorkerPoolSize int\n\n\tcustomerHostname string\n\tdriverHostname   string\n\tfrontendHostname string\n\trouteHostname    string\n\n\tcustomerPort int\n\tdriverPort   int\n\tfrontendPort int\n\troutePort    int\n\n\tbasepath string\n\tjaegerUI string\n)\n\n// used by root command\nfunc addFlags(cmd *cobra.Command) {\n\tcmd.PersistentFlags().StringVarP(&otelExporter, \"otel-exporter\", \"x\", \"otlp\", \"OpenTelemetry exporter (otlp|stdout)\")\n\n\tcmd.PersistentFlags().DurationVarP(&fixDBConnDelay, \"fix-db-query-delay\", \"D\", 300*time.Millisecond, \"Average latency of MySQL DB query\")\n\tcmd.PersistentFlags().BoolVarP(&fixDBConnDisableMutex, \"fix-disable-db-conn-mutex\", \"M\", false, \"Disables the mutex guarding db connection\")\n\tcmd.PersistentFlags().IntVarP(&fixRouteWorkerPoolSize, \"fix-route-worker-pool-size\", \"W\", 3, \"Default worker pool size\")\n\n\t// Add flags to choose hostnames for services\n\tcmd.PersistentFlags().StringVar(&customerHostname, \"customer-service-hostname\", \"0.0.0.0\", \"Hostname for customer service\")\n\tcmd.PersistentFlags().StringVar(&driverHostname, \"driver-service-hostname\", \"0.0.0.0\", \"Hostname for driver service\")\n\tcmd.PersistentFlags().StringVar(&frontendHostname, \"frontend-service-hostname\", \"0.0.0.0\", \"Hostname for frontend service\")\n\tcmd.PersistentFlags().StringVar(&routeHostname, \"route-service-hostname\", \"0.0.0.0\", \"Hostname for routing service\")\n\n\t// Add flags to choose ports for services\n\tcmd.PersistentFlags().IntVarP(&customerPort, \"customer-service-port\", \"c\", 8081, \"Port for customer service\")\n\tcmd.PersistentFlags().IntVarP(&driverPort, \"driver-service-port\", \"d\", 8082, \"Port for driver service\")\n\tcmd.PersistentFlags().IntVarP(&frontendPort, \"frontend-service-port\", \"f\", 8080, \"Port for frontend service\")\n\tcmd.PersistentFlags().IntVarP(&routePort, \"route-service-port\", \"r\", 8083, \"Port for routing service\")\n\n\t// Flag for serving frontend at custom basepath url\n\tcmd.PersistentFlags().StringVarP(&basepath, \"basepath\", \"b\", \"\", `Basepath for frontend service(default \"/\")`)\n\tcmd.PersistentFlags().StringVarP(&jaegerUI, \"jaeger-ui\", \"j\", \"http://localhost:16686\", \"Address of Jaeger UI to create [find trace] links\")\n\n\tcmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false, \"Enables debug logging\")\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/frontend.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/frontend\"\n)\n\n// frontendCmd represents the frontend command\nvar frontendCmd = &cobra.Command{\n\tUse:   \"frontend\",\n\tShort: \"Starts Frontend service\",\n\tLong:  `Starts Frontend service.`,\n\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\toptions.FrontendHostPort = net.JoinHostPort(frontendHostname, strconv.Itoa(frontendPort))\n\t\toptions.DriverHostPort = net.JoinHostPort(driverHostname, strconv.Itoa(driverPort))\n\t\toptions.CustomerHostPort = net.JoinHostPort(customerHostname, strconv.Itoa(customerPort))\n\t\toptions.RouteHostPort = net.JoinHostPort(routeHostname, strconv.Itoa(routePort))\n\t\toptions.Basepath = basepath\n\t\toptions.JaegerUI = jaegerUI\n\n\t\tzapLogger := logger.With(zap.String(\"service\", \"frontend\"))\n\t\tlogger := log.NewFactory(zapLogger)\n\t\tserver := frontend.NewServer(\n\t\t\toptions,\n\t\t\ttracing.InitOTEL(\"frontend\", otelExporter, metricsFactory, logger),\n\t\t\tlogger,\n\t\t)\n\t\treturn logError(zapLogger, server.Run())\n\t},\n}\n\nvar options frontend.ConfigOptions\n\nfunc init() {\n\tRootCmd.AddCommand(frontendCmd)\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/root.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/config\"\n\t\"github.com/jaegertracing/jaeger/internal/jaegerclientenv2otel\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics/prometheus\"\n)\n\nvar (\n\tlogger         *zap.Logger\n\tmetricsFactory metrics.Factory\n)\n\n// RootCmd represents the base command when called without any subcommands\nvar RootCmd = &cobra.Command{\n\tUse:   \"examples-hotrod\",\n\tShort: \"HotR.O.D. - A tracing demo application\",\n\tLong:  `HotR.O.D. - A tracing demo application.`,\n}\n\n// Execute adds all child commands to the root command sets flags appropriately.\n// This is called by main.main(). It only needs to happen once to the rootCmd.\nfunc Execute() {\n\tif err := RootCmd.Execute(); err != nil {\n\t\tlogger.Fatal(\"We bowled a googly\", zap.Error(err))\n\t\tos.Exit(-1)\n\t}\n}\n\nfunc init() {\n\taddFlags(RootCmd)\n\tcobra.OnInitialize(onInitialize)\n}\n\n// onInitialize is called before the command is executed.\nfunc onInitialize() {\n\tzapOptions := []zap.Option{\n\t\tzap.AddStacktrace(zapcore.FatalLevel),\n\t\tzap.AddCallerSkip(1),\n\t}\n\tif !verbose {\n\t\tzapOptions = append(zapOptions,\n\t\t\tzap.IncreaseLevel(zap.LevelEnablerFunc(func(l zapcore.Level) bool { return l != zapcore.DebugLevel })),\n\t\t)\n\t}\n\tlogger, _ = zap.NewDevelopment(zapOptions...)\n\n\tjaegerclientenv2otel.MapJaegerToOtelEnvVars(logger)\n\n\tmetricsFactory = prometheus.New().Namespace(metrics.NSOptions{Name: \"hotrod\", Tags: nil})\n\n\tif config.MySQLGetDelay != fixDBConnDelay {\n\t\tlogger.Info(\"fix: overriding MySQL query delay\", zap.Duration(\"old\", config.MySQLGetDelay), zap.Duration(\"new\", fixDBConnDelay))\n\t\tconfig.MySQLGetDelay = fixDBConnDelay\n\t}\n\tif fixDBConnDisableMutex {\n\t\tlogger.Info(\"fix: disabling db connection mutex\")\n\t\tconfig.MySQLMutexDisabled = true\n\t}\n\tif config.RouteWorkerPoolSize != fixRouteWorkerPoolSize {\n\t\tlogger.Info(\"fix: overriding route worker pool size\", zap.Int(\"old\", config.RouteWorkerPoolSize), zap.Int(\"new\", fixRouteWorkerPoolSize))\n\t\tconfig.RouteWorkerPoolSize = fixRouteWorkerPoolSize\n\t}\n\n\tif customerHostname != \"0.0.0.0\" {\n\t\tlogger.Info(\"changing customer service hostname\", zap.String(\"old\", \"0.0.0.0\"), zap.String(\"new\", customerHostname))\n\t}\n\n\tif driverHostname != \"0.0.0.0\" {\n\t\tlogger.Info(\"changing driver service hostname\", zap.String(\"old\", \"0.0.0.0\"), zap.String(\"new\", driverHostname))\n\t}\n\n\tif frontendHostname != \"0.0.0.0\" {\n\t\tlogger.Info(\"changing frontend service hostname\", zap.String(\"old\", \"0.0.0.0\"), zap.String(\"new\", frontendHostname))\n\t}\n\n\tif routeHostname != \"0.0.0.0\" {\n\t\tlogger.Info(\"changing route service hostname\", zap.String(\"old\", \"0.0.0.0\"), zap.String(\"new\", routeHostname))\n\t}\n\n\tif customerPort != 8081 {\n\t\tlogger.Info(\"changing customer service port\", zap.Int(\"old\", 8081), zap.Int(\"new\", customerPort))\n\t}\n\n\tif driverPort != 8082 {\n\t\tlogger.Info(\"changing driver service port\", zap.Int(\"old\", 8082), zap.Int(\"new\", driverPort))\n\t}\n\n\tif frontendPort != 8080 {\n\t\tlogger.Info(\"changing frontend service port\", zap.Int(\"old\", 8080), zap.Int(\"new\", frontendPort))\n\t}\n\n\tif routePort != 8083 {\n\t\tlogger.Info(\"changing route service port\", zap.Int(\"old\", 8083), zap.Int(\"new\", routePort))\n\t}\n\n\tif basepath != \"\" {\n\t\tlogger.Info(\"changing basepath for frontend\", zap.String(\"old\", \"/\"), zap.String(\"new\", basepath))\n\t}\n}\n\nfunc logError(logger *zap.Logger, err error) error {\n\tif err != nil {\n\t\tlogger.Error(\"Error running command\", zap.Error(err))\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "examples/hotrod/cmd/route.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cmd\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/route\"\n)\n\n// routeCmd represents the route command\nvar routeCmd = &cobra.Command{\n\tUse:   \"route\",\n\tShort: \"Starts Route service\",\n\tLong:  `Starts Route service.`,\n\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\tzapLogger := logger.With(zap.String(\"service\", \"route\"))\n\t\tlogger := log.NewFactory(zapLogger)\n\t\tserver := route.NewServer(\n\t\t\tnet.JoinHostPort(routeHostname, strconv.Itoa(routePort)),\n\t\t\ttracing.InitOTEL(\"route\", otelExporter, metricsFactory, logger),\n\t\t\tlogger,\n\t\t)\n\t\treturn logError(zapLogger, server.Run())\n\t},\n}\n\nfunc init() {\n\tRootCmd.AddCommand(routeCmd)\n}\n"
  },
  {
    "path": "examples/hotrod/docker-compose.yml",
    "content": "# To run a specific version of Jaeger, use environment variable, e.g.:\n#     JAEGER_VERSION=2.0.0 HOTROD_VERSION=1.63.0 docker compose up\n\nservices:\n  jaeger:\n    image: ${REGISTRY:-}jaegertracing/jaeger:${JAEGER_VERSION:-latest}\n    ports:\n      - \"16686:16686\"\n      - \"16687:16687\"\n      - \"4317:4317\"\n      - \"4318:4318\"\n    environment:\n      - LOG_LEVEL=debug\n    networks:\n      - jaeger-example\n\n  hotrod:\n    image: ${REGISTRY:-}jaegertracing/example-hotrod:${HOTROD_VERSION:-latest}\n    # To run the latest trunk build, find the tag at Docker Hub and use the line below\n    # https://hub.docker.com/r/jaegertracing/example-hotrod-snapshot/tags\n    #image: jaegertracing/example-hotrod-snapshot:0ab8f2fcb12ff0d10830c1ee3bb52b745522db6c\n    ports:\n      - \"8080:8080\"\n      - \"8083:8083\"\n    command: [\"all\"]\n    environment:\n      - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318\n    networks:\n      - jaeger-example\n    depends_on:\n      - jaeger\n\nnetworks:\n  jaeger-example:\n"
  },
  {
    "path": "examples/hotrod/kubernetes/README.md",
    "content": "# Hot R.O.D. - Rides on Demand on Kubernetes\n\nExample deployment for the [hotrod app](..) using Helm charts in your Kubernetes environment (Kind, minikube, k3s, EKS, GKE).\n\n## Prerequisites\n\n- Kubernetes cluster\n- Helm 3.x installed\n- kubectl configured\n\n## Usage\n\n### Deploy with Default Configuration\n\n```bash\ncd examples/oci\n./deploy-all.sh clean\n```\n\n### Deploy with Local Images (for development)\n\n```bash\n# For Jaeger v2 with local images\ncd examples/oci\n./deploy-all.sh local <image-tag>\n```\n\n### Deploy Modes\n\n- **`upgrade`** (default): Upgrade existing deployment or install if not present\n- **`local`**: Deploy using local registry images (localhost:5000)\n- **`clean`**: Clean install (removes existing deployment first)\n\n### Access Applications\n\nAfter deployment completes, use port-forwarding:\n\n```bash\n# Jaeger UI\nkubectl port-forward svc/jaeger-query 16686:16686\n\n# HotROD application\nkubectl port-forward svc/jaeger-hotrod 8080:80\n\n# Prometheus (optional)\nkubectl port-forward svc/prometheus 9090:9090\n\n# Grafana (optional)\nkubectl port-forward svc/prometheus-grafana 9091:80\n```\n\nThen access:\n- 🔍 **Jaeger UI**: http://localhost:16686/jaeger\n- 🚕 **HotROD App**: http://localhost:8080/hotrod\n- 📈 **Prometheus**: http://localhost:9090\n- 📊 **Grafana**: http://localhost:9091\n\n## Configuration\n\nThe deployment uses:\n- **Helm charts** from [jaeger-helm-charts](https://github.com/jaegertracing/helm-charts)\n- **Prometheus** for metrics collection\n- **Load generator** for creating sample traces\n\nConfiguration files:\n- `jaeger-values.yaml` - Jaeger Helm chart values\n- `config.yaml` - Jaeger configuration\n- `ui-config.json` - Jaeger UI configuration\n- `monitoring-values.yaml` - Prometheus configuration\n"
  },
  {
    "path": "examples/hotrod/main.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/cmd\"\n)\n\nfunc main() {\n\tcmd.Execute()\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/delay/delay.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage delay\n\nimport (\n\t\"math\"\n\t\"math/rand\"\n\t\"time\"\n)\n\n// Sleep generates a normally distributed random delay with given mean and stdDev\n// and blocks for that duration.\nfunc Sleep(mean time.Duration, stdDev time.Duration) {\n\tfMean := float64(mean)\n\tfStdDev := float64(stdDev)\n\tdelay := time.Duration(math.Max(1, rand.NormFloat64()*fStdDev+fMean))\n\ttime.Sleep(delay)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/delay/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage delay\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/httperr/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage httperr\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/httperr/httperr.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage httperr\n\nimport (\n\t\"net/http\"\n)\n\n// HandleError checks if the error is not nil, writes it to the output\n// with the specified status code, and returns true. If error is nil it returns false.\nfunc HandleError(w http.ResponseWriter, err error, statusCode int) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\thttp.Error(w, string(err.Error()), statusCode)\n\treturn true\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/log/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage log\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/log/factory.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage log\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// Factory is the default logging wrapper that can create\n// logger instances either for a given Context or context-less.\ntype Factory struct {\n\tlogger *zap.Logger\n}\n\n// NewFactory creates a new Factory.\nfunc NewFactory(logger *zap.Logger) Factory {\n\treturn Factory{logger: logger}\n}\n\n// Bg creates a context-unaware logger.\nfunc (b Factory) Bg() Logger {\n\treturn wrapper(b)\n}\n\n// For returns a context-aware Logger. If the context\n// contains a span, all logging calls are also\n// echo-ed into the span.\nfunc (b Factory) For(ctx context.Context) Logger {\n\tif span := trace.SpanFromContext(ctx); span != nil {\n\t\tlogger := spanLogger{span: span, logger: b.logger}\n\t\tlogger.spanFields = []zapcore.Field{\n\t\t\tzap.String(\"trace_id\", span.SpanContext().TraceID().String()),\n\t\t\tzap.String(\"span_id\", span.SpanContext().SpanID().String()),\n\t\t}\n\t\treturn logger\n\t}\n\treturn b.Bg()\n}\n\n// With creates a child logger, and optionally adds some context fields to that logger.\nfunc (b Factory) With(fields ...zapcore.Field) Factory {\n\treturn Factory{logger: b.logger.With(fields...)}\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/log/logger.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage log\n\nimport (\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// Logger is a simplified abstraction of the zap.Logger\ntype Logger interface {\n\tDebug(msg string, fields ...zapcore.Field)\n\tInfo(msg string, fields ...zapcore.Field)\n\tError(msg string, fields ...zapcore.Field)\n\tFatal(msg string, fields ...zapcore.Field)\n\tWith(fields ...zapcore.Field) Logger\n}\n\n// wrapper delegates all calls to the underlying zap.Logger\ntype wrapper struct {\n\tlogger *zap.Logger\n}\n\n// Debug logs an debug msg with fields\nfunc (l wrapper) Debug(msg string, fields ...zapcore.Field) {\n\tl.logger.Debug(msg, fields...)\n}\n\n// Info logs an info msg with fields\nfunc (l wrapper) Info(msg string, fields ...zapcore.Field) {\n\tl.logger.Info(msg, fields...)\n}\n\n// Error logs an error msg with fields\nfunc (l wrapper) Error(msg string, fields ...zapcore.Field) {\n\tl.logger.Error(msg, fields...)\n}\n\n// Fatal logs a fatal error msg with fields\nfunc (l wrapper) Fatal(msg string, fields ...zapcore.Field) {\n\tl.logger.Fatal(msg, fields...)\n}\n\n// With creates a child logger, and optionally adds some context fields to that logger.\nfunc (l wrapper) With(fields ...zapcore.Field) Logger {\n\treturn wrapper{logger: l.logger.With(fields...)}\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/log/spanlogger.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage log\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\ntype spanLogger struct {\n\tlogger     *zap.Logger\n\tspan       trace.Span\n\tspanFields []zapcore.Field\n}\n\nfunc (sl spanLogger) Debug(msg string, fields ...zapcore.Field) {\n\tsl.logToSpan(\"debug\", msg, fields...)\n\tsl.logger.Debug(msg, append(sl.spanFields, fields...)...)\n}\n\nfunc (sl spanLogger) Info(msg string, fields ...zapcore.Field) {\n\tsl.logToSpan(\"info\", msg, fields...)\n\tsl.logger.Info(msg, append(sl.spanFields, fields...)...)\n}\n\nfunc (sl spanLogger) Error(msg string, fields ...zapcore.Field) {\n\tsl.logToSpan(\"error\", msg, fields...)\n\tsl.logger.Error(msg, append(sl.spanFields, fields...)...)\n}\n\nfunc (sl spanLogger) Fatal(msg string, fields ...zapcore.Field) {\n\tsl.logToSpan(\"fatal\", msg, fields...)\n\tsl.span.SetStatus(codes.Error, msg)\n\tsl.logger.Fatal(msg, append(sl.spanFields, fields...)...)\n}\n\n// With creates a child logger, and optionally adds some context fields to that logger.\nfunc (sl spanLogger) With(fields ...zapcore.Field) Logger {\n\treturn spanLogger{logger: sl.logger.With(fields...), span: sl.span, spanFields: sl.spanFields}\n}\n\nfunc (sl spanLogger) logToSpan(level, msg string, fields ...zapcore.Field) {\n\tfields = append(fields, zap.String(\"level\", level))\n\tsl.span.AddEvent(\n\t\tmsg,\n\t\ttrace.WithAttributes(logFieldsToOTelAttrs(fields)...),\n\t)\n}\n\nfunc logFieldsToOTelAttrs(fields []zapcore.Field) []attribute.KeyValue {\n\tencoder := &bridgeFieldEncoder{}\n\tfor _, field := range fields {\n\t\tfield.AddTo(encoder)\n\t}\n\treturn encoder.pairs\n}\n\ntype bridgeFieldEncoder struct {\n\tpairs []attribute.KeyValue\n}\n\nfunc (e *bridgeFieldEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {\n\te.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(marshaler)))\n\treturn nil\n}\n\nfunc (e *bridgeFieldEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {\n\te.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(marshaler)))\n\treturn nil\n}\n\nfunc (e *bridgeFieldEncoder) AddBinary(key string, value []byte) {\n\te.pairs = append(e.pairs, attribute.String(key, string(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddByteString(key string, value []byte) {\n\te.pairs = append(e.pairs, attribute.String(key, string(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddBool(key string, value bool) {\n\te.pairs = append(e.pairs, attribute.Bool(key, value))\n}\n\nfunc (e *bridgeFieldEncoder) AddComplex128(key string, value complex128) {\n\te.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddComplex64(key string, value complex64) {\n\te.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddDuration(key string, value time.Duration) {\n\te.pairs = append(e.pairs, attribute.String(key, value.String()))\n}\n\nfunc (e *bridgeFieldEncoder) AddFloat64(key string, value float64) {\n\te.pairs = append(e.pairs, attribute.Float64(key, value))\n}\n\nfunc (e *bridgeFieldEncoder) AddFloat32(key string, value float32) {\n\te.pairs = append(e.pairs, attribute.Float64(key, float64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddInt(key string, value int) {\n\te.pairs = append(e.pairs, attribute.Int(key, value))\n}\n\nfunc (e *bridgeFieldEncoder) AddInt64(key string, value int64) {\n\te.pairs = append(e.pairs, attribute.Int64(key, value))\n}\n\nfunc (e *bridgeFieldEncoder) AddInt32(key string, value int32) {\n\te.pairs = append(e.pairs, attribute.Int64(key, int64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddInt16(key string, value int16) {\n\te.pairs = append(e.pairs, attribute.Int64(key, int64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddInt8(key string, value int8) {\n\te.pairs = append(e.pairs, attribute.Int64(key, int64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddString(key, value string) {\n\te.pairs = append(e.pairs, attribute.String(key, value))\n}\n\nfunc (e *bridgeFieldEncoder) AddTime(key string, value time.Time) {\n\te.pairs = append(e.pairs, attribute.String(key, value.String()))\n}\n\nfunc (e *bridgeFieldEncoder) AddUint(key string, value uint) {\n\te.pairs = append(e.pairs, attribute.String(key, strconv.FormatUint(uint64(value), 10)))\n}\n\nfunc (e *bridgeFieldEncoder) AddUint64(key string, value uint64) {\n\te.pairs = append(e.pairs, attribute.String(key, strconv.FormatUint(value, 10)))\n}\n\nfunc (e *bridgeFieldEncoder) AddUint32(key string, value uint32) {\n\te.pairs = append(e.pairs, attribute.Int64(key, int64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddUint16(key string, value uint16) {\n\te.pairs = append(e.pairs, attribute.Int64(key, int64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddUint8(key string, value uint8) {\n\te.pairs = append(e.pairs, attribute.Int64(key, int64(value)))\n}\n\nfunc (e *bridgeFieldEncoder) AddUintptr(key string, value uintptr) {\n\te.pairs = append(e.pairs, attribute.String(key, fmt.Sprint(value)))\n}\n\nfunc (*bridgeFieldEncoder) AddReflected(string /* key */, any /* value */) error { return nil }\nfunc (*bridgeFieldEncoder) OpenNamespace(string /* key */)                       {}\n"
  },
  {
    "path": "examples/hotrod/pkg/pool/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage pool\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/pool/pool.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage pool\n\n// Pool is a simple worker pool\ntype Pool struct {\n\tjobs chan func()\n\tstop chan struct{}\n}\n\n// New creates a new pool with the given number of workers\nfunc New(workers int) *Pool {\n\tjobs := make(chan func())\n\tstop := make(chan struct{})\n\tfor range workers {\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase job := <-jobs:\n\t\t\t\t\tjob()\n\t\t\t\tcase <-stop:\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\treturn &Pool{\n\t\tjobs: jobs,\n\t\tstop: stop,\n\t}\n}\n\n// Execute enqueues the job to be executed by one of the workers in the pool\nfunc (p *Pool) Execute(job func()) {\n\tp.jobs <- job\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/baggage.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracing\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel/baggage\"\n)\n\nfunc BaggageItem(ctx context.Context, key string) string {\n\tb := baggage.FromContext(ctx)\n\tm := b.Member(key)\n\treturn m.Value()\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracing\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/http.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracing\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\"\n\t\"go.opentelemetry.io/otel/trace\"\n)\n\n// HTTPClient wraps an http.Client with tracing instrumentation.\ntype HTTPClient struct {\n\tTracerProvider trace.TracerProvider\n\tClient         *http.Client\n}\n\nfunc NewHTTPClient(tp trace.TracerProvider) *HTTPClient {\n\treturn &HTTPClient{\n\t\tTracerProvider: tp,\n\t\tClient: &http.Client{\n\t\t\tTransport: otelhttp.NewTransport(\n\t\t\t\thttp.DefaultTransport,\n\t\t\t\totelhttp.WithTracerProvider(tp),\n\t\t\t),\n\t\t},\n\t}\n}\n\n// GetJSON executes HTTP GET against specified url and tried to parse\n// the response into out object.\nfunc (c *HTTPClient) GetJSON(ctx context.Context, _ string /* endpoint */, url string, out any) error {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tres, err := c.Client.Do(req) //nolint:gosec // G704 - URL from internal config\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer res.Body.Close()\n\n\tif res.StatusCode >= http.StatusBadRequest {\n\t\tbody, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn errors.New(string(body))\n\t}\n\n\tdecoder := json.NewDecoder(res.Body)\n\treturn decoder.Decode(out)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/init.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracing\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp\"\n\t\"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\t\"go.opentelemetry.io/otel/sdk/resource\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing/rpcmetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nvar once sync.Once\n\n// InitOTEL initializes OpenTelemetry SDK.\nfunc InitOTEL(serviceName string, exporterType string, metricsFactory metrics.Factory, logger log.Factory) trace.TracerProvider {\n\tonce.Do(func() {\n\t\totel.SetTextMapPropagator(\n\t\t\tpropagation.NewCompositeTextMapPropagator(\n\t\t\t\tpropagation.TraceContext{},\n\t\t\t\tpropagation.Baggage{},\n\t\t\t))\n\t})\n\n\texp, err := createOtelExporter(exporterType)\n\tif err != nil {\n\t\tlogger.Bg().Fatal(\"cannot create exporter\", zap.String(\"exporterType\", exporterType), zap.Error(err))\n\t}\n\tlogger.Bg().Debug(\"using \" + exporterType + \" trace exporter\")\n\n\trpcmetricsObserver := rpcmetrics.NewObserver(metricsFactory, rpcmetrics.DefaultNameNormalizer)\n\n\tres, err := resource.New(\n\t\tcontext.Background(),\n\t\tresource.WithSchemaURL(otelsemconv.SchemaURL),\n\t\tresource.WithAttributes(otelsemconv.ServiceNameAttribute(serviceName)),\n\t\tresource.WithTelemetrySDK(),\n\t\tresource.WithHost(),\n\t\tresource.WithOSType(),\n\t)\n\tif err != nil {\n\t\tlogger.Bg().Fatal(\"resource creation failed\", zap.Error(err))\n\t}\n\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithBatcher(exp, sdktrace.WithBatchTimeout(1000*time.Millisecond)),\n\t\tsdktrace.WithSpanProcessor(rpcmetricsObserver),\n\t\tsdktrace.WithResource(res),\n\t)\n\tlogger.Bg().Debug(\"Created OTEL tracer\", zap.String(\"service-name\", serviceName))\n\treturn tp\n}\n\n// withSecure instructs the client to use HTTPS scheme, instead of hotrod's desired default HTTP\nfunc withSecure() bool {\n\treturn strings.HasPrefix(os.Getenv(\"OTEL_EXPORTER_OTLP_ENDPOINT\"), \"https://\") ||\n\t\tstrings.ToLower(os.Getenv(\"OTEL_EXPORTER_OTLP_INSECURE\")) == \"false\"\n}\n\nfunc createOtelExporter(exporterType string) (sdktrace.SpanExporter, error) {\n\tvar exporter sdktrace.SpanExporter\n\tvar err error\n\tswitch exporterType {\n\tcase \"jaeger\":\n\t\treturn nil, errors.New(\"jaeger exporter is no longer supported, please use otlp\")\n\tcase \"otlp\":\n\t\tvar opts []otlptracehttp.Option\n\t\tif !withSecure() {\n\t\t\topts = []otlptracehttp.Option{otlptracehttp.WithInsecure()}\n\t\t}\n\t\texporter, err = otlptrace.New(\n\t\t\tcontext.Background(),\n\t\t\totlptracehttp.NewClient(opts...),\n\t\t)\n\tcase \"stdout\":\n\t\texporter, err = stdouttrace.New()\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unrecognized exporter type %s\", exporterType)\n\t}\n\treturn exporter, err\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/mutex.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n)\n\n// Mutex is just like the standard sync.Mutex, except that it is aware of the Context\n// and logs some diagnostic information into the current span.\ntype Mutex struct {\n\tSessionBaggageKey string\n\tLogFactory        log.Factory\n\n\trealLock sync.Mutex\n\tholder   string\n\n\twaiters     []string\n\twaitersLock sync.Mutex\n}\n\n// Lock acquires an exclusive lock.\nfunc (sm *Mutex) Lock(ctx context.Context) {\n\tlogger := sm.LogFactory.For(ctx)\n\tsession := BaggageItem(ctx, sm.SessionBaggageKey)\n\tactiveSpan := trace.SpanFromContext(ctx)\n\tactiveSpan.SetAttributes(attribute.String(sm.SessionBaggageKey, session))\n\n\tsm.waitersLock.Lock()\n\tif waiting := len(sm.waiters); waiting > 0 && activeSpan != nil {\n\t\tlogger.Info(\n\t\t\tfmt.Sprintf(\"Waiting for lock behind %d transactions\", waiting),\n\t\t\tzap.String(\"blockers\", fmt.Sprintf(\"%v\", sm.waiters)),\n\t\t)\n\t}\n\tsm.waiters = append(sm.waiters, session)\n\tsm.waitersLock.Unlock()\n\n\tsm.realLock.Lock()\n\tsm.holder = session\n\n\tsm.waitersLock.Lock()\n\tbehindLen := len(sm.waiters) - 1\n\tbehindIDs := fmt.Sprintf(\"%v\", sm.waiters[1:]) // skip self\n\tsm.waitersLock.Unlock()\n\n\tlogger.Info(\n\t\tfmt.Sprintf(\"Acquired lock; %d transactions waiting behind\", behindLen),\n\t\tzap.String(\"waiters\", behindIDs),\n\t)\n}\n\n// Unlock releases the lock.\nfunc (sm *Mutex) Unlock() {\n\tsm.waitersLock.Lock()\n\tfor i, v := range sm.waiters {\n\t\tif v == sm.holder {\n\t\t\tsm.waiters = append(sm.waiters[0:i], sm.waiters[i+1:]...)\n\t\t\tbreak\n\t\t}\n\t}\n\tsm.waitersLock.Unlock()\n\n\tsm.realLock.Unlock()\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/mux.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracing\n\nimport (\n\t\"net/http\"\n\n\t\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n)\n\n// NewServeMux creates a new TracedServeMux.\nfunc NewServeMux(copyBaggage bool, tracer trace.TracerProvider, logger log.Factory) *TracedServeMux {\n\treturn &TracedServeMux{\n\t\tmux:         http.NewServeMux(),\n\t\tcopyBaggage: copyBaggage,\n\t\ttracer:      tracer,\n\t\tlogger:      logger,\n\t}\n}\n\n// TracedServeMux is a wrapper around http.ServeMux that instruments handlers for tracing.\ntype TracedServeMux struct {\n\tmux         *http.ServeMux\n\tcopyBaggage bool\n\ttracer      trace.TracerProvider\n\tlogger      log.Factory\n}\n\n// Handle implements http.ServeMux#Handle, which is used to register new handler.\nfunc (tm *TracedServeMux) Handle(pattern string, handler http.Handler) {\n\ttm.logger.Bg().Debug(\"registering traced handler\", zap.String(\"endpoint\", pattern))\n\n\tmiddleware := otelhttp.NewHandler(\n\t\ttraceResponseHandler(handler),\n\t\tpattern,\n\t\totelhttp.WithTracerProvider(tm.tracer))\n\n\ttm.mux.Handle(pattern, middleware)\n}\n\n// ServeHTTP implements http.ServeMux#ServeHTTP.\nfunc (tm *TracedServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\ttm.mux.ServeHTTP(w, r)\n}\n\n// Returns a handler that generates a traceresponse header.\n// https://github.com/w3c/trace-context/blob/main/spec/21-http_response_header_format.md\nfunc traceResponseHandler(handler http.Handler) http.Handler {\n\t// We use the standard TraceContext propagator, since the formats are identical.\n\t// But the propagator uses \"traceparent\" header name, so we inject it into a map\n\t// `carrier` and then use the result to set the \"tracereponse\" header.\n\tvar prop propagation.TraceContext\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tcarrier := make(map[string]string)\n\t\tprop.Inject(r.Context(), propagation.MapCarrier(carrier))\n\t\tw.Header().Add(\"traceresponse\", carrier[\"traceparent\"])\n\t\thandler.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/README.md",
    "content": "Package rpcmetrics implements an OpenTelemetry SpanProcessor that can be used to emit RPC metrics.\n\nThis package is copied from jaeger-client-go and adapted to work with OpenTelemtery SDK.\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/endpoints.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport \"sync\"\n\n// normalizedEndpoints is a cache for endpointName -> safeName mappings.\ntype normalizedEndpoints struct {\n\tnames      map[string]string\n\tmaxSize    int\n\tnormalizer NameNormalizer\n\tmux        sync.RWMutex\n}\n\nfunc newNormalizedEndpoints(maxSize int, normalizer NameNormalizer) *normalizedEndpoints {\n\treturn &normalizedEndpoints{\n\t\tmaxSize:    maxSize,\n\t\tnormalizer: normalizer,\n\t\tnames:      make(map[string]string, maxSize),\n\t}\n}\n\n// normalize looks up the name in the cache, if not found it uses normalizer\n// to convert the name to a safe name. If called with more than maxSize unique\n// names it returns \"\" for all other names beyond those already cached.\nfunc (n *normalizedEndpoints) normalize(name string) string {\n\tn.mux.RLock()\n\tnorm, ok := n.names[name]\n\tl := len(n.names)\n\tn.mux.RUnlock()\n\tif ok {\n\t\treturn norm\n\t}\n\tif l >= n.maxSize {\n\t\treturn \"\"\n\t}\n\treturn n.normalizeWithLock(name)\n}\n\nfunc (n *normalizedEndpoints) normalizeWithLock(name string) string {\n\tnorm := n.normalizer.Normalize(name)\n\tn.mux.Lock()\n\tdefer n.mux.Unlock()\n\t// cache may have grown while we were not holding the lock\n\tif len(n.names) >= n.maxSize {\n\t\treturn \"\"\n\t}\n\tn.names[name] = norm\n\treturn norm\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/endpoints_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNormalizedEndpoints(t *testing.T) {\n\tn := newNormalizedEndpoints(1, DefaultNameNormalizer)\n\n\tassertLen := func(l int) {\n\t\tn.mux.RLock()\n\t\tdefer n.mux.RUnlock()\n\t\tassert.Len(t, n.names, l)\n\t}\n\n\tassert.Equal(t, \"ab_cd\", n.normalize(\"ab^cd\"), \"one translation\")\n\tassert.Equal(t, \"ab_cd\", n.normalize(\"ab^cd\"), \"cache hit\")\n\tassertLen(1)\n\tassert.Empty(t, n.normalize(\"xys\"), \"cache overflow\")\n\tassertLen(1)\n}\n\nfunc TestNormalizedEndpointsDoubleLocking(t *testing.T) {\n\tn := newNormalizedEndpoints(1, DefaultNameNormalizer)\n\tassert.Equal(t, \"ab_cd\", n.normalize(\"ab^cd\"), \"fill out the cache\")\n\tassert.Empty(t, n.normalizeWithLock(\"xys\"), \"cache overflow\")\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/metrics.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\nconst (\n\totherEndpointsPlaceholder = \"other\"\n\tendpointNameMetricTag     = \"endpoint\"\n)\n\n// Metrics is a collection of metrics for an endpoint describing\n// throughput, success, errors, and performance.\ntype Metrics struct {\n\t// RequestCountSuccess is a counter of the total number of successes.\n\tRequestCountSuccess metrics.Counter `metric:\"requests\" tags:\"error=false\"`\n\n\t// RequestCountFailures is a counter of the number of times any failure has been observed.\n\tRequestCountFailures metrics.Counter `metric:\"requests\" tags:\"error=true\"`\n\n\t// RequestLatencySuccess is a latency histogram of successful requests.\n\tRequestLatencySuccess metrics.Timer `metric:\"request_latency\" tags:\"error=false\"`\n\n\t// RequestLatencyFailures is a latency histogram of failed requests.\n\tRequestLatencyFailures metrics.Timer `metric:\"request_latency\" tags:\"error=true\"`\n\n\t// HTTPStatusCode2xx is a counter of the total number of requests with HTTP status code 200-299\n\tHTTPStatusCode2xx metrics.Counter `metric:\"http_requests\" tags:\"status_code=2xx\"`\n\n\t// HTTPStatusCode3xx is a counter of the total number of requests with HTTP status code 300-399\n\tHTTPStatusCode3xx metrics.Counter `metric:\"http_requests\" tags:\"status_code=3xx\"`\n\n\t// HTTPStatusCode4xx is a counter of the total number of requests with HTTP status code 400-499\n\tHTTPStatusCode4xx metrics.Counter `metric:\"http_requests\" tags:\"status_code=4xx\"`\n\n\t// HTTPStatusCode5xx is a counter of the total number of requests with HTTP status code 500-599\n\tHTTPStatusCode5xx metrics.Counter `metric:\"http_requests\" tags:\"status_code=5xx\"`\n}\n\nfunc (m *Metrics) recordHTTPStatusCode(statusCode int64) {\n\tswitch {\n\tcase statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices:\n\t\tm.HTTPStatusCode2xx.Inc(1)\n\tcase statusCode >= http.StatusMultipleChoices && statusCode < http.StatusBadRequest:\n\t\tm.HTTPStatusCode3xx.Inc(1)\n\tcase statusCode >= http.StatusBadRequest && statusCode < http.StatusInternalServerError:\n\t\tm.HTTPStatusCode4xx.Inc(1)\n\tcase statusCode >= http.StatusInternalServerError && statusCode < 600:\n\t\tm.HTTPStatusCode5xx.Inc(1)\n\tdefault:\n\t\t// Status codes outside standard ranges (< 200 or >= 600) are ignored\n\t}\n}\n\n// MetricsByEndpoint is a registry/cache of metrics for each unique endpoint name.\n// Only maxNumberOfEndpoints Metrics are stored, all other endpoint names are mapped\n// to a generic endpoint name \"other\".\ntype MetricsByEndpoint struct {\n\tmetricsFactory    metrics.Factory\n\tendpoints         *normalizedEndpoints\n\tmetricsByEndpoint map[string]*Metrics\n\tmux               sync.RWMutex\n}\n\nfunc newMetricsByEndpoint(\n\tmetricsFactory metrics.Factory,\n\tnormalizer NameNormalizer,\n\tmaxNumberOfEndpoints int,\n) *MetricsByEndpoint {\n\treturn &MetricsByEndpoint{\n\t\tmetricsFactory:    metricsFactory,\n\t\tendpoints:         newNormalizedEndpoints(maxNumberOfEndpoints, normalizer),\n\t\tmetricsByEndpoint: make(map[string]*Metrics, maxNumberOfEndpoints+1), // +1 for \"other\"\n\t}\n}\n\nfunc (m *MetricsByEndpoint) get(endpoint string) *Metrics {\n\tsafeName := m.endpoints.normalize(endpoint)\n\tif safeName == \"\" {\n\t\tsafeName = otherEndpointsPlaceholder\n\t}\n\tm.mux.RLock()\n\tmet := m.metricsByEndpoint[safeName]\n\tm.mux.RUnlock()\n\tif met != nil {\n\t\treturn met\n\t}\n\n\treturn m.getWithWriteLock(safeName)\n}\n\n// split to make easier to test\nfunc (m *MetricsByEndpoint) getWithWriteLock(safeName string) *Metrics {\n\tm.mux.Lock()\n\tdefer m.mux.Unlock()\n\n\t// it is possible that the name has been already registered after we released\n\t// the read lock and before we grabbed the write lock, so check for that.\n\tif met, ok := m.metricsByEndpoint[safeName]; ok {\n\t\treturn met\n\t}\n\n\t// it would be nice to create the struct before locking, since Init() is somewhat\n\t// expensive, however some metrics backends (e.g. expvar) may not like duplicate metrics.\n\tmet := &Metrics{}\n\ttags := map[string]string{endpointNameMetricTag: safeName}\n\tmetrics.Init(met, m.metricsFactory, tags)\n\n\tm.metricsByEndpoint[safeName] = met\n\treturn met\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/metrics_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n)\n\n// E.g. tags(\"key\", \"value\", \"key\", \"value\")\nfunc tags(kv ...string) map[string]string {\n\tm := make(map[string]string)\n\tfor i := 0; i < len(kv)-1; i += 2 {\n\t\tm[kv[i]] = kv[i+1]\n\t}\n\treturn m\n}\n\nfunc endpointTags(endpoint string, kv ...string) map[string]string {\n\treturn tags(append([]string{\"endpoint\", endpoint}, kv...)...)\n}\n\nfunc TestMetricsByEndpoint(t *testing.T) {\n\tmet := metricstest.NewFactory(0)\n\tmbe := newMetricsByEndpoint(met, DefaultNameNormalizer, 2)\n\n\tm1 := mbe.get(\"abc1\")\n\tm2 := mbe.get(\"abc1\")               // from cache\n\tm2a := mbe.getWithWriteLock(\"abc1\") // from cache in double-checked lock\n\tassert.Equal(t, m1, m2)\n\tassert.Equal(t, m1, m2a)\n\n\tm3 := mbe.get(\"abc3\")\n\tm4 := mbe.get(\"overflow\")\n\tm5 := mbe.get(\"overflow2\")\n\n\tfor _, m := range []*Metrics{m1, m2, m2a, m3, m4, m5} {\n\t\tm.RequestCountSuccess.Inc(1)\n\t}\n\n\tmet.AssertCounterMetrics(t,\n\t\tmetricstest.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"abc1\", \"error\", \"false\"), Value: 3},\n\t\tmetricstest.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"abc3\", \"error\", \"false\"), Value: 1},\n\t\tmetricstest.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"other\", \"error\", \"false\"), Value: 2},\n\t)\n}\n\nfunc TestRecordHTTPStatusCode_DefaultCase(t *testing.T) {\n\tmet := metricstest.NewFactory(0)\n\tmbe := newMetricsByEndpoint(met, DefaultNameNormalizer, 2)\n\tmetrics := mbe.get(\"test-endpoint\")\n\n\tmetrics.recordHTTPStatusCode(100)\n\tmetrics.recordHTTPStatusCode(199)\n\tmetrics.recordHTTPStatusCode(600)\n\tmetrics.recordHTTPStatusCode(999)\n\n\tmet.AssertCounterMetrics(t)\n\n\tmetrics.recordHTTPStatusCode(200)\n\tmetrics.recordHTTPStatusCode(404)\n\tmetrics.recordHTTPStatusCode(500)\n\n\tmet.AssertCounterMetrics(t,\n\t\tmetricstest.ExpectedMetric{Name: \"http_requests\", Tags: endpointTags(\"test_endpoint\", \"status_code\", \"2xx\"), Value: 1},\n\t\tmetricstest.ExpectedMetric{Name: \"http_requests\", Tags: endpointTags(\"test_endpoint\", \"status_code\", \"4xx\"), Value: 1},\n\t\tmetricstest.ExpectedMetric{Name: \"http_requests\", Tags: endpointTags(\"test_endpoint\", \"status_code\", \"5xx\"), Value: 1},\n\t)\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/normalizer.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\n// NameNormalizer is used to convert the endpoint names to strings\n// that can be safely used as tags in the metrics.\ntype NameNormalizer interface {\n\tNormalize(name string) string\n}\n\n// DefaultNameNormalizer converts endpoint names so that they contain only characters\n// from the safe charset [a-zA-Z0-9./_]. All other characters are replaced with '_'.\nvar DefaultNameNormalizer = &SimpleNameNormalizer{\n\tSafeSets: []SafeCharacterSet{\n\t\t&Range{From: 'a', To: 'z'},\n\t\t&Range{From: 'A', To: 'Z'},\n\t\t&Range{From: '0', To: '9'},\n\t\t&Char{'_'},\n\t\t&Char{'/'},\n\t\t&Char{'.'},\n\t},\n\tReplacement: '_',\n}\n\n// SimpleNameNormalizer uses a set of safe character sets.\ntype SimpleNameNormalizer struct {\n\tSafeSets    []SafeCharacterSet\n\tReplacement byte\n}\n\n// SafeCharacterSet determines if the given character is \"safe\"\ntype SafeCharacterSet interface {\n\tIsSafe(c byte) bool\n}\n\n// Range implements SafeCharacterSet\ntype Range struct {\n\tFrom, To byte\n}\n\n// IsSafe implements SafeCharacterSet\nfunc (r *Range) IsSafe(c byte) bool {\n\treturn c >= r.From && c <= r.To\n}\n\n// Char implements SafeCharacterSet\ntype Char struct {\n\tVal byte\n}\n\n// IsSafe implements SafeCharacterSet\nfunc (ch *Char) IsSafe(c byte) bool {\n\treturn c == ch.Val\n}\n\n// Normalize checks each character in the string against SafeSets,\n// and if it's not safe substitutes it with Replacement.\nfunc (n *SimpleNameNormalizer) Normalize(name string) string {\n\tvar retMe []byte\n\tnameBytes := []byte(name)\n\tfor i, b := range nameBytes {\n\t\tif n.safeByte(b) {\n\t\t\tif retMe != nil {\n\t\t\t\tretMe[i] = b\n\t\t\t}\n\t\t} else {\n\t\t\tif retMe == nil {\n\t\t\t\tretMe = make([]byte, len(nameBytes))\n\t\t\t\tcopy(retMe[0:i], nameBytes[0:i])\n\t\t\t}\n\t\t\tretMe[i] = n.Replacement\n\t\t}\n\t}\n\tif retMe == nil {\n\t\treturn name\n\t}\n\treturn string(retMe)\n}\n\n// safeByte checks if b against all safe charsets.\nfunc (n *SimpleNameNormalizer) safeByte(b byte) bool {\n\tfor i := range n.SafeSets {\n\t\tif n.SafeSets[i].IsSafe(b) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/normalizer_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSimpleNameNormalizer(t *testing.T) {\n\tn := &SimpleNameNormalizer{\n\t\tSafeSets: []SafeCharacterSet{\n\t\t\t&Range{From: 'a', To: 'z'},\n\t\t\t&Char{'-'},\n\t\t},\n\t\tReplacement: '-',\n\t}\n\tassert.Equal(t, \"ab-cd\", n.Normalize(\"ab-cd\"), \"all valid\")\n\tassert.Equal(t, \"ab-cd\", n.Normalize(\"ab.cd\"), \"single mismatch\")\n\tassert.Equal(t, \"a--cd\", n.Normalize(\"aB-cd\"), \"range letter mismatch\")\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/observer.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nconst defaultMaxNumberOfEndpoints = 200\n\nvar _ sdktrace.SpanProcessor = (*Observer)(nil)\n\n// Observer is an observer that can emit RPC metrics.\ntype Observer struct {\n\tmetricsByEndpoint *MetricsByEndpoint\n}\n\n// NewObserver creates a new observer that can emit RPC metrics.\nfunc NewObserver(metricsFactory metrics.Factory, normalizer NameNormalizer) *Observer {\n\treturn &Observer{\n\t\tmetricsByEndpoint: newMetricsByEndpoint(\n\t\t\tmetricsFactory,\n\t\t\tnormalizer,\n\t\t\tdefaultMaxNumberOfEndpoints,\n\t\t),\n\t}\n}\n\nfunc (*Observer) OnStart(context.Context /* parent */, sdktrace.ReadWriteSpan) {}\n\nfunc (o *Observer) OnEnd(sp sdktrace.ReadOnlySpan) {\n\toperationName := sp.Name()\n\tif operationName == \"\" {\n\t\treturn\n\t}\n\tif sp.SpanKind() != trace.SpanKindServer {\n\t\treturn\n\t}\n\n\tmets := o.metricsByEndpoint.get(operationName)\n\tlatency := sp.EndTime().Sub(sp.StartTime())\n\n\tif status := sp.Status(); status.Code == codes.Error {\n\t\tmets.RequestCountFailures.Inc(1)\n\t\tmets.RequestLatencyFailures.Record(latency)\n\t} else {\n\t\tmets.RequestCountSuccess.Inc(1)\n\t\tmets.RequestLatencySuccess.Record(latency)\n\t}\n\tfor _, attr := range sp.Attributes() {\n\t\tif string(attr.Key) == string(otelsemconv.HTTPResponseStatusCodeKey) {\n\t\t\tif attr.Value.Type() == attribute.INT64 {\n\t\t\t\tmets.recordHTTPStatusCode(attr.Value.AsInt64())\n\t\t\t} else if attr.Value.Type() == attribute.STRING {\n\t\t\t\ts := attr.Value.AsString()\n\t\t\t\tif n, err := strconv.Atoi(s); err == nil {\n\t\t\t\t\tmets.recordHTTPStatusCode(int64(n))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (*Observer) Shutdown(context.Context) error {\n\treturn nil\n}\n\nfunc (*Observer) ForceFlush(context.Context) error {\n\treturn nil\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/observer_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/sdk/resource\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\n\tu \"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\ntype testTracer struct {\n\tmetrics *u.Factory\n\ttracer  trace.Tracer\n}\n\nfunc withTestTracer(runTest func(tt *testTracer)) {\n\tmetrics := u.NewFactory(time.Minute)\n\tdefer metrics.Stop()\n\tobserver := NewObserver(metrics, DefaultNameNormalizer)\n\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithSpanProcessor(observer),\n\t\tsdktrace.WithResource(resource.NewWithAttributes(\n\t\t\totelsemconv.SchemaURL,\n\t\t\totelsemconv.ServiceNameAttribute(\"test\"),\n\t\t)),\n\t)\n\trunTest(&testTracer{\n\t\tmetrics: metrics,\n\t\ttracer:  tp.Tracer(\"test\"),\n\t})\n}\n\nfunc TestObserver(t *testing.T) {\n\twithTestTracer(func(testTracer *testTracer) {\n\t\tts := time.Now()\n\t\tfinishOptions := trace.WithTimestamp(ts.Add((50 * time.Millisecond)))\n\n\t\ttestCases := []struct {\n\t\t\tname           string\n\t\t\tspanKind       trace.SpanKind\n\t\t\topNameOverride string\n\t\t\terr            bool\n\t\t}{\n\t\t\t{name: \"local-span\", spanKind: trace.SpanKindInternal},\n\t\t\t{name: \"get-user\", spanKind: trace.SpanKindServer},\n\t\t\t{name: \"get-user\", spanKind: trace.SpanKindServer, opNameOverride: \"get-user-override\"},\n\t\t\t{name: \"get-user\", spanKind: trace.SpanKindServer, err: true},\n\t\t\t{name: \"get-user-client\", spanKind: trace.SpanKindClient},\n\t\t}\n\n\t\tfor _, testCase := range testCases {\n\t\t\t_, span := testTracer.tracer.Start(\n\t\t\t\tcontext.Background(),\n\t\t\t\ttestCase.name, trace.WithSpanKind(testCase.spanKind),\n\t\t\t\ttrace.WithTimestamp(ts),\n\t\t\t)\n\t\t\tif testCase.opNameOverride != \"\" {\n\t\t\t\tspan.SetName(testCase.opNameOverride)\n\t\t\t}\n\t\t\tif testCase.err {\n\t\t\t\tspan.SetStatus(codes.Error, \"An error occurred\")\n\t\t\t}\n\t\t\tspan.End(finishOptions)\n\t\t}\n\n\t\ttestTracer.metrics.AssertCounterMetrics(t,\n\t\t\tu.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"local_span\", \"error\", \"false\"), Value: 0},\n\t\t\tu.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"get_user\", \"error\", \"false\"), Value: 1},\n\t\t\tu.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"get_user\", \"error\", \"true\"), Value: 1},\n\t\t\tu.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"get_user_override\", \"error\", \"false\"), Value: 1},\n\t\t\tu.ExpectedMetric{Name: \"requests\", Tags: endpointTags(\"get_user_client\", \"error\", \"false\"), Value: 0},\n\t\t)\n\t\ttestTracer.metrics.AssertTimerMetrics(t,\n\t\t\tu.ExpectedTimerMetric{Name: \"request_latency\", Tags: endpointTags(\"get_user\", \"error\", \"false\"), Percentile: \"P99\", Value: 50},\n\t\t\tu.ExpectedTimerMetric{Name: \"request_latency\", Tags: endpointTags(\"get_user\", \"error\", \"true\"), Percentile: \"P99\", Value: 50},\n\t\t)\n\t})\n}\n\nfunc TestTags(t *testing.T) {\n\ttype tagTestCase struct {\n\t\tattr    attribute.KeyValue\n\t\terr     bool\n\t\tmetrics []u.ExpectedMetric\n\t}\n\ttestCases := []tagTestCase{\n\t\t{err: false, metrics: []u.ExpectedMetric{\n\t\t\t{Name: \"requests\", Value: 1, Tags: tags(\"error\", \"false\")},\n\t\t}},\n\t\t{err: true, metrics: []u.ExpectedMetric{\n\t\t\t{Name: \"requests\", Value: 1, Tags: tags(\"error\", \"true\")},\n\t\t}},\n\t}\n\n\tfor i := 200; i <= 500; i += 100 {\n\t\ttestCases = append(testCases, tagTestCase{\n\t\t\tattr: otelsemconv.HTTPResponseStatusCode(i),\n\t\t\tmetrics: []u.ExpectedMetric{\n\t\t\t\t{Name: \"http_requests\", Value: 1, Tags: tags(\"status_code\", fmt.Sprintf(\"%dxx\", i/100))},\n\t\t\t},\n\t\t})\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tfor i := range testCase.metrics {\n\t\t\ttestCase.metrics[i].Tags[\"endpoint\"] = \"span\"\n\t\t}\n\t\tt.Run(fmt.Sprintf(\"%s-%v\", testCase.attr.Key, testCase.attr.Value), func(t *testing.T) {\n\t\t\twithTestTracer(func(testTracer *testTracer) {\n\t\t\t\t_, span := testTracer.tracer.Start(\n\t\t\t\t\tcontext.Background(),\n\t\t\t\t\t\"span\", trace.WithSpanKind(trace.SpanKindServer),\n\t\t\t\t)\n\t\t\t\tspan.SetAttributes(testCase.attr)\n\t\t\t\tif testCase.err {\n\t\t\t\t\tspan.SetStatus(codes.Error, \"An error occurred\")\n\t\t\t\t}\n\t\t\t\tspan.End()\n\t\t\t\ttestTracer.metrics.AssertCounterMetrics(t, testCase.metrics...)\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "examples/hotrod/pkg/tracing/rpcmetrics/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage rpcmetrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/services/config/config.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"time\"\n)\n\nvar (\n\t// 'frontend' service\n\n\t// RouteWorkerPoolSize is the size of the worker pool used to query `route` service.\n\t// Can be overwritten from command line.\n\tRouteWorkerPoolSize = 3\n\n\t// 'customer' service\n\n\t// MySQLGetDelay is how long retrieving a customer record takes.\n\t// Using large value mostly because I cannot click the button fast enough to cause a queue.\n\tMySQLGetDelay = 300 * time.Millisecond\n\n\t// MySQLGetDelayStdDev is standard deviation\n\tMySQLGetDelayStdDev = MySQLGetDelay / 10\n\n\t// MySQLMutexDisabled controls whether there is a mutex guarding db query execution.\n\t// When not disabled it simulates a misconfigured connection pool of size 1.\n\tMySQLMutexDisabled = false\n\n\t// 'driver' service\n\n\t// RedisFindDelay is how long finding closest drivers takes.\n\tRedisFindDelay = 20 * time.Millisecond\n\n\t// RedisFindDelayStdDev is standard deviation.\n\tRedisFindDelayStdDev = RedisFindDelay / 4\n\n\t// RedisGetDelay is how long retrieving a driver record takes.\n\tRedisGetDelay = 10 * time.Millisecond\n\n\t// RedisGetDelayStdDev is standard deviation\n\tRedisGetDelayStdDev = RedisGetDelay / 4\n\n\t// 'route' service\n\n\t// RouteCalcDelay is how long a route calculation takes\n\tRouteCalcDelay = 50 * time.Millisecond\n\n\t// RouteCalcDelayStdDev is standard deviation\n\tRouteCalcDelayStdDev = RouteCalcDelay / 4\n)\n"
  },
  {
    "path": "examples/hotrod/services/config/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/services/customer/client.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage customer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n)\n\n// Client is a remote client that implements customer.Interface\ntype Client struct {\n\tlogger   log.Factory\n\tclient   *tracing.HTTPClient\n\thostPort string\n}\n\n// NewClient creates a new customer.Client\nfunc NewClient(tracer trace.TracerProvider, logger log.Factory, hostPort string) *Client {\n\treturn &Client{\n\t\tlogger:   logger,\n\t\tclient:   tracing.NewHTTPClient(tracer),\n\t\thostPort: hostPort,\n\t}\n}\n\n// Get implements customer.Interface#Get as an RPC\nfunc (c *Client) Get(ctx context.Context, customerID int) (*Customer, error) {\n\tc.logger.For(ctx).Info(\"Getting customer\", zap.Int(\"customer_id\", customerID))\n\n\turl := fmt.Sprintf(\"http://\"+c.hostPort+\"/customer?customer=%d\", customerID)\n\tvar customer Customer\n\tif err := c.client.GetJSON(ctx, \"/customer\", url, &customer); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &customer, nil\n}\n"
  },
  {
    "path": "examples/hotrod/services/customer/database.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage customer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/delay\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/config\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\n// database simulates Customer repository implemented on top of an SQL database\ntype database struct {\n\ttracer    trace.Tracer\n\tlogger    log.Factory\n\tcustomers map[int]*Customer\n\tlock      *tracing.Mutex\n}\n\nfunc newDatabase(tracer trace.Tracer, logger log.Factory) *database {\n\treturn &database{\n\t\ttracer: tracer,\n\t\tlogger: logger,\n\t\tlock: &tracing.Mutex{\n\t\t\tSessionBaggageKey: \"request\",\n\t\t\tLogFactory:        logger,\n\t\t},\n\t\tcustomers: map[int]*Customer{\n\t\t\t123: {\n\t\t\t\tID:       \"123\",\n\t\t\t\tName:     \"Rachel's_Floral_Designs\",\n\t\t\t\tLocation: \"115,277\",\n\t\t\t},\n\t\t\t567: {\n\t\t\t\tID:       \"567\",\n\t\t\t\tName:     \"Amazing_Coffee_Roasters\",\n\t\t\t\tLocation: \"211,653\",\n\t\t\t},\n\t\t\t392: {\n\t\t\t\tID:       \"392\",\n\t\t\t\tName:     \"Trom_Chocolatier\",\n\t\t\t\tLocation: \"577,322\",\n\t\t\t},\n\t\t\t731: {\n\t\t\t\tID:       \"731\",\n\t\t\t\tName:     \"Japanese_Desserts\",\n\t\t\t\tLocation: \"728,326\",\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (d *database) Get(ctx context.Context, customerID int) (*Customer, error) {\n\td.logger.For(ctx).Info(\"Loading customer\", zap.Int(\"customer_id\", customerID))\n\n\tctx, span := d.tracer.Start(ctx, \"SQL SELECT\", trace.WithSpanKind(trace.SpanKindClient))\n\tspan.SetAttributes(\n\t\totelsemconv.PeerServiceAttribute(\"mysql\"),\n\t\tattribute.\n\t\t\tKey(\"sql.query\").\n\t\t\tString(fmt.Sprintf(\"SELECT * FROM customer WHERE customer_id=%d\", customerID)),\n\t)\n\tdefer span.End()\n\n\tif !config.MySQLMutexDisabled {\n\t\t// simulate misconfigured connection pool that only gives one connection at a time\n\t\td.lock.Lock(ctx)\n\t\tdefer d.lock.Unlock()\n\t}\n\n\t// simulate RPC delay\n\tdelay.Sleep(config.MySQLGetDelay, config.MySQLGetDelayStdDev)\n\n\tif customer, ok := d.customers[customerID]; ok {\n\t\treturn customer, nil\n\t}\n\treturn nil, errors.New(\"invalid customer ID\")\n}\n"
  },
  {
    "path": "examples/hotrod/services/customer/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage customer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/services/customer/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage customer\n\nimport (\n\t\"context\"\n)\n\n// Customer contains data about a customer.\ntype Customer struct {\n\tID       string\n\tName     string\n\tLocation string\n}\n\n// Interface exposed by the Customer service.\ntype Interface interface {\n\tGet(ctx context.Context, customerID int) (*Customer, error)\n}\n"
  },
  {
    "path": "examples/hotrod/services/customer/server.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage customer\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/httperr\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\n// Server implements Customer service\ntype Server struct {\n\thostPort string\n\ttracer   trace.TracerProvider\n\tlogger   log.Factory\n\tdatabase *database\n}\n\n// NewServer creates a new customer.Server\nfunc NewServer(hostPort string, otelExporter string, metricsFactory metrics.Factory, logger log.Factory) *Server {\n\treturn &Server{\n\t\thostPort: hostPort,\n\t\ttracer:   tracing.InitOTEL(\"customer\", otelExporter, metricsFactory, logger),\n\t\tlogger:   logger,\n\t\tdatabase: newDatabase(\n\t\t\ttracing.InitOTEL(\"mysql\", otelExporter, metricsFactory, logger).Tracer(\"mysql\"),\n\t\t\tlogger.With(zap.String(\"component\", \"mysql\")),\n\t\t),\n\t}\n}\n\n// Run starts the Customer server\nfunc (s *Server) Run() error {\n\tmux := s.createServeMux()\n\ts.logger.Bg().Info(\"Starting\", zap.String(\"address\", \"http://\"+s.hostPort))\n\tserver := &http.Server{\n\t\tAddr:              s.hostPort,\n\t\tHandler:           mux,\n\t\tReadHeaderTimeout: 3 * time.Second,\n\t}\n\treturn server.ListenAndServe()\n}\n\nfunc (s *Server) createServeMux() http.Handler {\n\tmux := tracing.NewServeMux(false, s.tracer, s.logger)\n\tmux.Handle(\"/customer\", http.HandlerFunc(s.customer))\n\treturn mux\n}\n\nfunc (s *Server) customer(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\ts.logger.For(ctx).Info(\"HTTP request received\", zap.String(\"method\", r.Method), zap.Stringer(\"url\", r.URL))\n\tif err := r.ParseForm(); httperr.HandleError(w, err, http.StatusBadRequest) {\n\t\ts.logger.For(ctx).Error(\"bad request\", zap.Error(err))\n\t\treturn\n\t}\n\n\tcustomer := r.Form.Get(\"customer\")\n\tif customer == \"\" {\n\t\thttp.Error(w, \"Missing required 'customer' parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tcustomerID, err := strconv.Atoi(customer)\n\tif err != nil {\n\t\thttp.Error(w, \"Parameter 'customer' is not an integer\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tresponse, err := s.database.Get(ctx, customerID)\n\tif httperr.HandleError(w, err, http.StatusInternalServerError) {\n\t\ts.logger.For(ctx).Error(\"request failed\", zap.Error(err))\n\t\treturn\n\t}\n\n\tdata, err := json.Marshal(response)\n\tif httperr.HandleError(w, err, http.StatusInternalServerError) {\n\t\ts.logger.For(ctx).Error(\"cannot marshal response\", zap.Error(err))\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(data) //nolint:gosec // G705 - writing JSON response\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/client.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage driver\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\"\n\t\"go.opentelemetry.io/otel/metric/noop\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n)\n\n// Client is a remote client that implements driver.Interface\ntype Client struct {\n\tlogger log.Factory\n\tclient DriverServiceClient\n}\n\n// NewClient creates a new driver.Client\nfunc NewClient(tracerProvider trace.TracerProvider, logger log.Factory, hostPort string) *Client {\n\tconn, err := grpc.NewClient(hostPort,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithStatsHandler(otelgrpc.NewClientHandler(\n\t\t\totelgrpc.WithTracerProvider(tracerProvider),\n\t\t\totelgrpc.WithMeterProvider(noop.NewMeterProvider()),\n\t\t)),\n\t)\n\tif err != nil {\n\t\tlogger.Bg().Fatal(\"Cannot create gRPC connection\", zap.Error(err))\n\t}\n\n\tclient := NewDriverServiceClient(conn)\n\treturn &Client{\n\t\tlogger: logger,\n\t\tclient: client,\n\t}\n}\n\n// FindNearest implements driver.Interface#FindNearest as an RPC\nfunc (c *Client) FindNearest(ctx context.Context, location string) ([]Driver, error) {\n\tc.logger.For(ctx).Info(\"Finding nearest drivers\", zap.String(\"location\", location))\n\tctx, cancel := context.WithTimeout(ctx, 1*time.Second)\n\tdefer cancel()\n\tresponse, err := c.client.FindNearest(ctx, &DriverLocationRequest{Location: location})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fromProto(response), nil\n}\n\nfunc fromProto(response *DriverLocationResponse) []Driver {\n\tretMe := make([]Driver, len(response.Locations))\n\tfor i, result := range response.Locations {\n\t\tretMe[i] = Driver{\n\t\t\tDriverID: result.DriverID,\n\t\t\tLocation: result.Location,\n\t\t}\n\t}\n\treturn retMe\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/driver.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: examples/hotrod/services/driver/driver.proto\n\npackage driver\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\ntype DriverLocationRequest struct {\n\tLocation             string   `protobuf:\"bytes,1,opt,name=location,proto3\" json:\"location,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *DriverLocationRequest) Reset()         { *m = DriverLocationRequest{} }\nfunc (m *DriverLocationRequest) String() string { return proto.CompactTextString(m) }\nfunc (*DriverLocationRequest) ProtoMessage()    {}\nfunc (*DriverLocationRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_cdcd28b7ebdcd54f, []int{0}\n}\nfunc (m *DriverLocationRequest) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_DriverLocationRequest.Unmarshal(m, b)\n}\nfunc (m *DriverLocationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_DriverLocationRequest.Marshal(b, m, deterministic)\n}\nfunc (m *DriverLocationRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_DriverLocationRequest.Merge(m, src)\n}\nfunc (m *DriverLocationRequest) XXX_Size() int {\n\treturn xxx_messageInfo_DriverLocationRequest.Size(m)\n}\nfunc (m *DriverLocationRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_DriverLocationRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_DriverLocationRequest proto.InternalMessageInfo\n\nfunc (m *DriverLocationRequest) GetLocation() string {\n\tif m != nil {\n\t\treturn m.Location\n\t}\n\treturn \"\"\n}\n\ntype DriverLocation struct {\n\tDriverID             string   `protobuf:\"bytes,1,opt,name=driverID,proto3\" json:\"driverID,omitempty\"`\n\tLocation             string   `protobuf:\"bytes,2,opt,name=location,proto3\" json:\"location,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *DriverLocation) Reset()         { *m = DriverLocation{} }\nfunc (m *DriverLocation) String() string { return proto.CompactTextString(m) }\nfunc (*DriverLocation) ProtoMessage()    {}\nfunc (*DriverLocation) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_cdcd28b7ebdcd54f, []int{1}\n}\nfunc (m *DriverLocation) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_DriverLocation.Unmarshal(m, b)\n}\nfunc (m *DriverLocation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_DriverLocation.Marshal(b, m, deterministic)\n}\nfunc (m *DriverLocation) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_DriverLocation.Merge(m, src)\n}\nfunc (m *DriverLocation) XXX_Size() int {\n\treturn xxx_messageInfo_DriverLocation.Size(m)\n}\nfunc (m *DriverLocation) XXX_DiscardUnknown() {\n\txxx_messageInfo_DriverLocation.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_DriverLocation proto.InternalMessageInfo\n\nfunc (m *DriverLocation) GetDriverID() string {\n\tif m != nil {\n\t\treturn m.DriverID\n\t}\n\treturn \"\"\n}\n\nfunc (m *DriverLocation) GetLocation() string {\n\tif m != nil {\n\t\treturn m.Location\n\t}\n\treturn \"\"\n}\n\ntype DriverLocationResponse struct {\n\tLocations            []*DriverLocation `protobuf:\"bytes,1,rep,name=locations,proto3\" json:\"locations,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}          `json:\"-\"`\n\tXXX_unrecognized     []byte            `json:\"-\"`\n\tXXX_sizecache        int32             `json:\"-\"`\n}\n\nfunc (m *DriverLocationResponse) Reset()         { *m = DriverLocationResponse{} }\nfunc (m *DriverLocationResponse) String() string { return proto.CompactTextString(m) }\nfunc (*DriverLocationResponse) ProtoMessage()    {}\nfunc (*DriverLocationResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_cdcd28b7ebdcd54f, []int{2}\n}\nfunc (m *DriverLocationResponse) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_DriverLocationResponse.Unmarshal(m, b)\n}\nfunc (m *DriverLocationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_DriverLocationResponse.Marshal(b, m, deterministic)\n}\nfunc (m *DriverLocationResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_DriverLocationResponse.Merge(m, src)\n}\nfunc (m *DriverLocationResponse) XXX_Size() int {\n\treturn xxx_messageInfo_DriverLocationResponse.Size(m)\n}\nfunc (m *DriverLocationResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_DriverLocationResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_DriverLocationResponse proto.InternalMessageInfo\n\nfunc (m *DriverLocationResponse) GetLocations() []*DriverLocation {\n\tif m != nil {\n\t\treturn m.Locations\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*DriverLocationRequest)(nil), \"driver.DriverLocationRequest\")\n\tproto.RegisterType((*DriverLocation)(nil), \"driver.DriverLocation\")\n\tproto.RegisterType((*DriverLocationResponse)(nil), \"driver.DriverLocationResponse\")\n}\n\nfunc init() {\n\tproto.RegisterFile(\"examples/hotrod/services/driver/driver.proto\", fileDescriptor_cdcd28b7ebdcd54f)\n}\n\nvar fileDescriptor_cdcd28b7ebdcd54f = []byte{\n\t// 207 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x49, 0xad, 0x48, 0xcc,\n\t0x2d, 0xc8, 0x49, 0x2d, 0xd6, 0xcf, 0xc8, 0x2f, 0x29, 0xca, 0x4f, 0xd1, 0x2f, 0x4e, 0x2d, 0x2a,\n\t0xcb, 0x4c, 0x4e, 0x2d, 0xd6, 0x4f, 0x29, 0xca, 0x2c, 0x4b, 0x2d, 0x82, 0x52, 0x7a, 0x05, 0x45,\n\t0xf9, 0x25, 0xf9, 0x42, 0x6c, 0x10, 0x9e, 0x92, 0x31, 0x97, 0xa8, 0x0b, 0x98, 0xe5, 0x93, 0x9f,\n\t0x9c, 0x58, 0x92, 0x99, 0x9f, 0x17, 0x94, 0x5a, 0x58, 0x9a, 0x5a, 0x5c, 0x22, 0x24, 0xc5, 0xc5,\n\t0x91, 0x03, 0x15, 0x92, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0xf3, 0x95, 0x3c, 0xb8, 0xf8,\n\t0x50, 0x35, 0x81, 0x54, 0x43, 0x0c, 0xf4, 0x74, 0x81, 0xa9, 0x86, 0xf1, 0x51, 0x4c, 0x62, 0x42,\n\t0x33, 0xc9, 0x8f, 0x4b, 0x0c, 0xdd, 0xfa, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x13, 0x2e,\n\t0x4e, 0x98, 0xaa, 0x62, 0x09, 0x46, 0x05, 0x66, 0x0d, 0x6e, 0x23, 0x31, 0x3d, 0xa8, 0x17, 0xd0,\n\t0xb4, 0x20, 0x14, 0x1a, 0xc5, 0x72, 0xf1, 0x42, 0x24, 0x83, 0x21, 0x9e, 0x17, 0xf2, 0xe1, 0xe2,\n\t0x76, 0xcb, 0xcc, 0x4b, 0xf1, 0x4b, 0x4d, 0x2c, 0x02, 0xf9, 0x4a, 0x16, 0x87, 0x11, 0x10, 0x4f,\n\t0x4b, 0xc9, 0xe1, 0x92, 0x86, 0x38, 0xca, 0x89, 0x23, 0x0a, 0x1a, 0x6e, 0x49, 0x6c, 0xe0, 0x60,\n\t0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x90, 0x0b, 0x66, 0x76, 0x01, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// DriverServiceClient is the client API for DriverService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype DriverServiceClient interface {\n\tFindNearest(ctx context.Context, in *DriverLocationRequest, opts ...grpc.CallOption) (*DriverLocationResponse, error)\n}\n\ntype driverServiceClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewDriverServiceClient(cc *grpc.ClientConn) DriverServiceClient {\n\treturn &driverServiceClient{cc}\n}\n\nfunc (c *driverServiceClient) FindNearest(ctx context.Context, in *DriverLocationRequest, opts ...grpc.CallOption) (*DriverLocationResponse, error) {\n\tout := new(DriverLocationResponse)\n\terr := c.cc.Invoke(ctx, \"/driver.DriverService/FindNearest\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// DriverServiceServer is the server API for DriverService service.\ntype DriverServiceServer interface {\n\tFindNearest(context.Context, *DriverLocationRequest) (*DriverLocationResponse, error)\n}\n\n// UnimplementedDriverServiceServer can be embedded to have forward compatible implementations.\ntype UnimplementedDriverServiceServer struct {\n}\n\nfunc (*UnimplementedDriverServiceServer) FindNearest(ctx context.Context, req *DriverLocationRequest) (*DriverLocationResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method FindNearest not implemented\")\n}\n\nfunc RegisterDriverServiceServer(s *grpc.Server, srv DriverServiceServer) {\n\ts.RegisterService(&_DriverService_serviceDesc, srv)\n}\n\nfunc _DriverService_FindNearest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DriverLocationRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DriverServiceServer).FindNearest(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/driver.DriverService/FindNearest\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DriverServiceServer).FindNearest(ctx, req.(*DriverLocationRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _DriverService_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"driver.DriverService\",\n\tHandlerType: (*DriverServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"FindNearest\",\n\t\t\tHandler:    _DriverService_FindNearest_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"examples/hotrod/services/driver/driver.proto\",\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/driver.proto",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax=\"proto3\";\npackage driver;\n\noption go_package = \"driver\";\n\nmessage DriverLocationRequest {\n  string location = 1;\n}\n\nmessage DriverLocation {\n  string driverID = 1;\n  string location = 2;\n}\n\nmessage DriverLocationResponse {\n  repeated DriverLocation locations = 1;\n}\n\nservice DriverService {\n  rpc FindNearest(DriverLocationRequest) returns (DriverLocationResponse);\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage driver\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage driver\n\nimport (\n\t\"context\"\n)\n\n// Driver describes a driver and the current car location.\ntype Driver struct {\n\tDriverID string\n\tLocation string\n}\n\n// Interface exposed by the Driver service.\ntype Interface interface {\n\tFindNearest(ctx context.Context, location string) ([]Driver, error)\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/redis.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage driver\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/delay\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/config\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\n// Redis is a simulator of remote Redis cache\ntype Redis struct {\n\ttracer trace.Tracer // simulate redis as a separate process\n\tlogger log.Factory\n\terrorSimulator\n}\n\nfunc newRedis(otelExporter string, metricsFactory metrics.Factory, logger log.Factory) *Redis {\n\ttp := tracing.InitOTEL(\"redis-manual\", otelExporter, metricsFactory, logger)\n\treturn &Redis{\n\t\ttracer: tp.Tracer(\"redis-manual\"),\n\t\tlogger: logger,\n\t}\n}\n\n// FindDriverIDs finds IDs of drivers who are near the location.\nfunc (r *Redis) FindDriverIDs(ctx context.Context, location string) []string {\n\tctx, span := r.tracer.Start(ctx, \"FindDriverIDs\", trace.WithSpanKind(trace.SpanKindClient))\n\tspan.SetAttributes(attribute.Key(\"param.driver.location\").String(location))\n\tdefer span.End()\n\n\t// simulate RPC delay\n\tdelay.Sleep(config.RedisFindDelay, config.RedisFindDelayStdDev)\n\n\tdrivers := make([]string, 10)\n\tfor i := range drivers {\n\t\t// #nosec\n\t\tdrivers[i] = fmt.Sprintf(\"T7%05dC\", rand.Int()%100000)\n\t}\n\tr.logger.For(ctx).Info(\"Found drivers\", zap.Strings(\"drivers\", drivers))\n\treturn drivers\n}\n\n// GetDriver returns driver and the current car location\nfunc (r *Redis) GetDriver(ctx context.Context, driverID string) (Driver, error) {\n\tctx, span := r.tracer.Start(ctx, \"GetDriver\", trace.WithSpanKind(trace.SpanKindClient))\n\tspan.SetAttributes(attribute.Key(\"param.driverID\").String(driverID))\n\tdefer span.End()\n\n\t// simulate RPC delay\n\tdelay.Sleep(config.RedisGetDelay, config.RedisGetDelayStdDev)\n\tif err := r.checkError(); err != nil {\n\t\tspan.RecordError(err)\n\t\tspan.SetStatus(codes.Error, \"An error occurred\")\n\t\tr.logger.For(ctx).Error(\"redis timeout\", zap.String(\"driver_id\", driverID), zap.Error(err))\n\t\treturn Driver{}, err\n\t}\n\n\tr.logger.For(ctx).Info(\"Got driver's ID\", zap.String(\"driverID\", driverID))\n\n\t// #nosec\n\treturn Driver{\n\t\tDriverID: driverID,\n\t\tLocation: fmt.Sprintf(\"%d,%d\", rand.Int()%1000, rand.Int()%1000),\n\t}, nil\n}\n\nvar errTimeout = errors.New(\"redis timeout\")\n\ntype errorSimulator struct {\n\tsync.Mutex\n\tcountTillError int\n}\n\nfunc (es *errorSimulator) checkError() error {\n\tes.Lock()\n\tes.countTillError--\n\tif es.countTillError > 0 {\n\t\tes.Unlock()\n\t\treturn nil\n\t}\n\tes.countTillError = 5\n\tes.Unlock()\n\tdelay.Sleep(2*config.RedisGetDelay, 0) // add more delay for \"timeout\"\n\treturn errTimeout\n}\n"
  },
  {
    "path": "examples/hotrod/services/driver/server.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage driver\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net\"\n\n\t\"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\"\n\t\"go.opentelemetry.io/otel/metric/noop\"\n\t\"go.uber.org/zap\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\n// Server implements jaeger-demo-frontend service\ntype Server struct {\n\thostPort string\n\tlogger   log.Factory\n\tredis    *Redis\n\tserver   *grpc.Server\n}\n\nvar _ DriverServiceServer = (*Server)(nil)\n\n// NewServer creates a new driver.Server\nfunc NewServer(hostPort string, otelExporter string, metricsFactory metrics.Factory, logger log.Factory) *Server {\n\ttracerProvider := tracing.InitOTEL(\"driver\", otelExporter, metricsFactory, logger)\n\tserver := grpc.NewServer(\n\t\tgrpc.StatsHandler(otelgrpc.NewServerHandler(\n\t\t\totelgrpc.WithTracerProvider(tracerProvider),\n\t\t\totelgrpc.WithMeterProvider(noop.NewMeterProvider()),\n\t\t)),\n\t)\n\treturn &Server{\n\t\thostPort: hostPort,\n\t\tlogger:   logger,\n\t\tserver:   server,\n\t\tredis:    newRedis(otelExporter, metricsFactory, logger),\n\t}\n}\n\n// Run starts the Driver server\nfunc (s *Server) Run() error {\n\tlis, err := (&net.ListenConfig{}).Listen(context.Background(), \"tcp\", s.hostPort)\n\tif err != nil {\n\t\ts.logger.Bg().Fatal(\"Unable to create http listener\", zap.Error(err))\n\t}\n\tRegisterDriverServiceServer(s.server, s)\n\ts.logger.Bg().Info(\"Starting\", zap.String(\"address\", s.hostPort), zap.String(\"type\", \"gRPC\"))\n\terr = s.server.Serve(lis)\n\tif err != nil {\n\t\ts.logger.Bg().Fatal(\"Unable to start gRPC server\", zap.Error(err))\n\t}\n\treturn err\n}\n\n// FindNearest implements gRPC driver interface\nfunc (s *Server) FindNearest(ctx context.Context, location *DriverLocationRequest) (*DriverLocationResponse, error) {\n\ts.logger.For(ctx).Info(\"Searching for nearby drivers\", zap.String(\"location\", location.Location))\n\tdriverIDs := s.redis.FindDriverIDs(ctx, location.Location)\n\n\tlocations := make([]*DriverLocation, len(driverIDs))\n\tfor i, driverID := range driverIDs {\n\t\tvar drv Driver\n\t\tvar err error\n\t\tfor i := range 3 {\n\t\t\tdrv, err = s.redis.GetDriver(ctx, driverID)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ts.logger.For(ctx).Error(\"Retrying GetDriver after error\", zap.Int(\"retry_no\", i+1), zap.Error(err))\n\t\t}\n\t\tif err != nil {\n\t\t\ts.logger.For(ctx).Error(\"Failed to get driver after 3 attempts\", zap.Error(err))\n\t\t\treturn nil, err\n\t\t}\n\t\tlocations[i] = &DriverLocation{\n\t\t\tDriverID: drv.DriverID,\n\t\t\tLocation: drv.Location,\n\t\t}\n\t}\n\ts.logger.For(ctx).Info(\n\t\t\"Search successful\",\n\t\tzap.Int(\"driver_count\", len(locations)),\n\t\tzap.String(\"locations\", toJSON(locations)),\n\t)\n\treturn &DriverLocationResponse{Locations: locations}, nil\n}\n\nfunc toJSON(v any) string {\n\tstr, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\treturn string(str)\n}\n"
  },
  {
    "path": "examples/hotrod/services/frontend/best_eta.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage frontend\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/baggage\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/pool\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/config\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/customer\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/driver\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/route\"\n)\n\ntype bestETA struct {\n\tcustomer customer.Interface\n\tdriver   driver.Interface\n\troute    route.Interface\n\tpool     *pool.Pool\n\tlogger   log.Factory\n}\n\n// Response contains ETA for a trip.\ntype Response struct {\n\tDriver string\n\tETA    time.Duration\n}\n\nfunc newBestETA(tracer trace.TracerProvider, logger log.Factory, options ConfigOptions) *bestETA {\n\treturn &bestETA{\n\t\tcustomer: customer.NewClient(\n\t\t\ttracer,\n\t\t\tlogger.With(zap.String(\"component\", \"customer_client\")),\n\t\t\toptions.CustomerHostPort,\n\t\t),\n\t\tdriver: driver.NewClient(\n\t\t\ttracer,\n\t\t\tlogger.With(zap.String(\"component\", \"driver_client\")),\n\t\t\toptions.DriverHostPort,\n\t\t),\n\t\troute: route.NewClient(\n\t\t\ttracer,\n\t\t\tlogger.With(zap.String(\"component\", \"route_client\")),\n\t\t\toptions.RouteHostPort,\n\t\t),\n\t\tpool:   pool.New(config.RouteWorkerPoolSize),\n\t\tlogger: logger,\n\t}\n}\n\nfunc (eta *bestETA) Get(ctx context.Context, customerID int) (*Response, error) {\n\tcust, err := eta.customer.Get(ctx, customerID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\teta.logger.For(ctx).Info(\"Found customer\", zap.Any(\"customer\", cust))\n\n\tm, err := baggage.NewMember(\"customer\", cust.Name)\n\tif err != nil {\n\t\teta.logger.For(ctx).Error(\"cannot create baggage member\", zap.Error(err))\n\t}\n\tbag := baggage.FromContext(ctx)\n\tbag, err = bag.SetMember(m)\n\tif err != nil {\n\t\teta.logger.For(ctx).Error(\"cannot set baggage member\", zap.Error(err))\n\t}\n\tctx = baggage.ContextWithBaggage(ctx, bag)\n\n\tdrivers, err := eta.driver.FindNearest(ctx, cust.Location)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\teta.logger.For(ctx).Info(\"Found drivers\", zap.Any(\"drivers\", drivers))\n\n\tresults := eta.getRoutes(ctx, cust, drivers)\n\teta.logger.For(ctx).Info(\"Found routes\", zap.Any(\"routes\", results))\n\n\tresp := &Response{ETA: math.MaxInt64}\n\tfor _, result := range results {\n\t\tif result.err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif result.route.ETA < resp.ETA {\n\t\t\tresp.ETA = result.route.ETA\n\t\t\tresp.Driver = result.driver\n\t\t}\n\t}\n\tif resp.Driver == \"\" {\n\t\treturn nil, errors.New(\"no routes found\")\n\t}\n\n\teta.logger.For(ctx).Info(\"Dispatch successful\", zap.String(\"driver\", resp.Driver), zap.String(\"eta\", resp.ETA.String()))\n\treturn resp, nil\n}\n\ntype routeResult struct {\n\tdriver string\n\troute  *route.Route\n\terr    error\n}\n\n// getRoutes calls Route service for each (customer, driver) pair\nfunc (eta *bestETA) getRoutes(ctx context.Context, cust *customer.Customer, drivers []driver.Driver) []routeResult {\n\tresults := make([]routeResult, 0, len(drivers))\n\twg := sync.WaitGroup{}\n\troutesLock := sync.Mutex{}\n\tfor _, dd := range drivers {\n\t\twg.Add(1)\n\t\tdrv := dd // capture loop var\n\t\t// Use worker pool to (potentially) execute requests in parallel\n\t\teta.pool.Execute(func() {\n\t\t\troute, err := eta.route.FindRoute(ctx, drv.Location, cust.Location)\n\t\t\troutesLock.Lock()\n\t\t\tresults = append(results, routeResult{\n\t\t\t\tdriver: drv.DriverID,\n\t\t\t\troute:  route,\n\t\t\t\terr:    err,\n\t\t\t})\n\t\t\troutesLock.Unlock()\n\t\t\twg.Done()\n\t\t})\n\t}\n\twg.Wait()\n\treturn results\n}\n"
  },
  {
    "path": "examples/hotrod/services/frontend/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage frontend\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/services/frontend/server.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage frontend\n\nimport (\n\t\"embed\"\n\t\"encoding/json\"\n\t\"expvar\"\n\t\"net/http\"\n\t\"path\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/httperr\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/internal/httpfs\"\n)\n\n//go:embed web_assets/*\nvar assetFS embed.FS\n\n// Server implements jaeger-demo-frontend service\ntype Server struct {\n\thostPort string\n\ttracer   trace.TracerProvider\n\tlogger   log.Factory\n\tbestETA  *bestETA\n\tassetFS  http.FileSystem\n\tbasepath string\n\tjaegerUI string\n}\n\n// ConfigOptions used to make sure service clients\n// can find correct server ports\ntype ConfigOptions struct {\n\tFrontendHostPort string\n\tDriverHostPort   string\n\tCustomerHostPort string\n\tRouteHostPort    string\n\tBasepath         string\n\tJaegerUI         string\n}\n\n// NewServer creates a new frontend.Server\nfunc NewServer(options ConfigOptions, tracer trace.TracerProvider, logger log.Factory) *Server {\n\treturn &Server{\n\t\thostPort: options.FrontendHostPort,\n\t\ttracer:   tracer,\n\t\tlogger:   logger,\n\t\tbestETA:  newBestETA(tracer, logger, options),\n\t\tassetFS:  httpfs.PrefixedFS(\"web_assets\", http.FS(assetFS)),\n\t\tbasepath: options.Basepath,\n\t\tjaegerUI: options.JaegerUI,\n\t}\n}\n\n// Run starts the frontend server\nfunc (s *Server) Run() error {\n\tmux := s.createServeMux()\n\ts.logger.Bg().Info(\"Starting\", zap.String(\"address\", \"http://\"+path.Join(s.hostPort, s.basepath)))\n\tserver := &http.Server{\n\t\tAddr:              s.hostPort,\n\t\tHandler:           mux,\n\t\tReadHeaderTimeout: 3 * time.Second,\n\t}\n\treturn server.ListenAndServe()\n}\n\nfunc (s *Server) createServeMux() http.Handler {\n\tmux := tracing.NewServeMux(true, s.tracer, s.logger)\n\tp := path.Join(\"/\", s.basepath)\n\tmux.Handle(p, http.StripPrefix(p, http.FileServer(s.assetFS)))\n\tmux.Handle(path.Join(p, \"/dispatch\"), http.HandlerFunc(s.dispatch))\n\tmux.Handle(path.Join(p, \"/config\"), http.HandlerFunc(s.config))\n\tmux.Handle(path.Join(p, \"/debug/vars\"), expvar.Handler()) // expvar\n\tmux.Handle(path.Join(p, \"/metrics\"), promhttp.Handler())  // Prometheus\n\treturn mux\n}\n\nfunc (s *Server) config(w http.ResponseWriter, r *http.Request) {\n\tconfig := map[string]string{\n\t\t\"jaeger\": s.jaegerUI,\n\t}\n\ts.writeResponse(config, w, r)\n}\n\nfunc (s *Server) dispatch(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\ts.logger.For(ctx).Info(\"HTTP request received\", zap.String(\"method\", r.Method), zap.Stringer(\"url\", r.URL))\n\tif err := r.ParseForm(); httperr.HandleError(w, err, http.StatusBadRequest) {\n\t\ts.logger.For(ctx).Error(\"bad request\", zap.Error(err))\n\t\treturn\n\t}\n\n\tcustomer := r.Form.Get(\"customer\")\n\tif customer == \"\" {\n\t\thttp.Error(w, \"Missing required 'customer' parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tcustomerID, err := strconv.Atoi(customer)\n\tif err != nil {\n\t\thttp.Error(w, \"Parameter 'customer' is not an integer\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// TODO distinguish between user errors (such as invalid customer ID) and server failures\n\tresponse, err := s.bestETA.Get(ctx, customerID)\n\tif httperr.HandleError(w, err, http.StatusInternalServerError) {\n\t\ts.logger.For(ctx).Error(\"request failed\", zap.Error(err))\n\t\treturn\n\t}\n\n\ts.writeResponse(response, w, r)\n}\n\nfunc (s *Server) writeResponse(response any, w http.ResponseWriter, r *http.Request) {\n\tdata, err := json.Marshal(response)\n\tif httperr.HandleError(w, err, http.StatusInternalServerError) {\n\t\ts.logger.For(r.Context()).Error(\"cannot marshal response\", zap.Error(err))\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(data) //nolint:gosec // G705 - writing JSON response\n}\n"
  },
  {
    "path": "examples/hotrod/services/frontend/web_assets/index.html",
    "content": "<html>\n  <meta charset=\"ISO-8859-1\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">\n  <head>\n    <title>HotROD - Rides On Demand</title>\n    <script src=\"https://cdn.jsdelivr.net/npm/jquery@3.7.0/dist/jquery.min.js\"></script>\n    <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\" integrity=\"sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u\" crossorigin=\"anonymous\">\n    <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\" integrity=\"sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa\" crossorigin=\"anonymous\"></script>\n\n    <style>\n.uuid { margin-top: 15px; }\n.hotrod-button { padding: 20px; cursor: pointer; margin-top: 10px; }\n.hotrod-button:hover { cursor: pointer; filter: brightness(85%); }\n#hotrod-log { margin-top: 15px; }\n#tip { margin-top: 15px; }\n    </style>\n\n  </head>\n  <body>\n    <div class=\"container\">\n      <div class=\"uuid alert alert-info\"></div>\n      <center>\n        <h1>Hot R.O.D.</h1>\n        <h4>🚗 <em>Rides On Demand</em> 🚗</h4>\n        <div class=\"row\">\n            <div class=\"col-md-3 col-sm-6\">\n                <span\n                    class=\"btn btn-info btn-block hotrod-button\"\n                    data-customer=\"123\">Rachel's Floral Designs</span>\n            </div>\n            <div class=\"col-md-3 col-sm-6\">\n                <span\n                    class=\"btn btn-info btn-block hotrod-button\"\n                    data-customer=\"392\">Trom Chocolatier</span>\n            </div>\n            <div class=\"col-md-3 col-sm-6\">\n                <span\n                    class=\"btn btn-info btn-block hotrod-button\"\n                    data-customer=\"731\">Japanese Desserts</span>\n            </div>\n            <div class=\"col-md-3 col-sm-6\">\n                <span\n                    class=\"btn btn-info btn-block hotrod-button\"\n                    data-customer=\"567\">Amazing Coffee Roasters</span>\n            </div>\n        </div>\n        <div id=\"tip\">Click on customer name above to order a car.</div>\n        <div id=\"hotrod-log\" class=\"lead\"></div>\n      </center>\n    </div>\n  </body>\n\n  <script>\n\nfunction formatDuration(duration) {\n  const d = duration / (1000000 * 1000 * 60);\n  const units = 'min';\n  return Math.round(d) + units;\n}\n\nfunction parseTraceResponse(value) {\n  const VERSION = '00';\n  const VERSION_PART = '(?!ff)[\\\\da-f]{2}';\n  const TRACE_ID_PART = '(?![0]{32})[\\\\da-f]{32}';\n  const PARENT_ID_PART = '(?![0]{16})[\\\\da-f]{16}';\n  const FLAGS_PART = '[\\\\da-f]{2}';\n  const TRACE_PARENT_REGEX = new RegExp(\n    `^\\\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\\\s?$`\n  );\n  const match = TRACE_PARENT_REGEX.exec(value);\n  return (match) ? match[2] : null;\n}\n\nconst clientUUID = Math.round(Math.random() * 10000);\nvar lastRequestID = 0;\n\n$(\".uuid\").html(`Your web client's id: <strong>${clientUUID}</strong>`);\n\n$(\".hotrod-button\").click(function(evt) {\n  lastRequestID++;\n  const requestID = clientUUID + \"-\" + lastRequestID;\n  const freshCar = $($(\"#hotrod-log\").prepend('<div class=\"fresh-car\"><em>Dispatching a car...[req: '+requestID+']</em></div>').children()[0]);\n  const customer = evt.target.dataset.customer;\n  const headers = {\n      'baggage': 'session=' + clientUUID + ', request=' + requestID\n  };\n  console.log('sending headers', headers);\n\n  // Use current URI as basepath for ajax requests\n  var pathPrefix = window.location.pathname;\n  pathPrefix = pathPrefix != \"/\" ? pathPrefix : '';\n\n  // TODO this should be done on page load, not on every button click\n  var config = {};\n  $.ajax(pathPrefix + '/config?nonse=' + Math.random(), {\n    method: 'GET',\n    success: function(data, textStatus) {\n      console.log('config', data);\n      config = data;\n    },\n  });\n\n  const before = Date.now();\n  $.ajax(pathPrefix + '/dispatch?customer=' + customer + '&nonse=' + Math.random(), {\n    headers: headers,\n    method: 'GET',\n    success: function(data, textStatus, xhr) {\n      const after = Date.now();\n      const traceResponse = xhr.getResponseHeader('traceresponse');\n      const traceID = parseTraceResponse(traceResponse);\n      console.log('response', data);\n      console.log('traceResponse', traceResponse, 'traceID', traceID);\n\n      const duration = formatDuration(data.ETA);\n      var traceLink = '';\n      if (config && config['jaeger']) {\n        const jaeger = config['jaeger'];\n        const findURL = `/search?limit=20&lookback=1h&service=frontend&tags=%7B%22driver%22%3A%22${data.Driver}%22%7D`;\n        traceLink = ` [<a href=\"${jaeger}${findURL}\" target=\"_blank\">find trace</a>]`;\n        if (traceID) {\n          traceLink += ` [<a href=\"${jaeger}/trace/${traceID}\" target=\"_blank\">open trace</a>]`;\n        }\n      }\n      freshCar.html(`HotROD <b>${data.Driver}</b> arriving in ${duration} [req: ${requestID}, latency: ${after-before}ms] ${traceLink}`);\n    },\n  });\n});\n\n  </script>\n\n</html>\n"
  },
  {
    "path": "examples/hotrod/services/route/client.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage route\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n)\n\n// Client is a remote client that implements route.Interface\ntype Client struct {\n\tlogger   log.Factory\n\tclient   *tracing.HTTPClient\n\thostPort string\n}\n\n// NewClient creates a new route.Client\nfunc NewClient(tracer trace.TracerProvider, logger log.Factory, hostPort string) *Client {\n\treturn &Client{\n\t\tlogger:   logger,\n\t\tclient:   tracing.NewHTTPClient(tracer),\n\t\thostPort: hostPort,\n\t}\n}\n\n// FindRoute implements route.Interface#FindRoute as an RPC\nfunc (c *Client) FindRoute(ctx context.Context, pickup, dropoff string) (*Route, error) {\n\tc.logger.For(ctx).Info(\"Finding route\", zap.String(\"pickup\", pickup), zap.String(\"dropoff\", dropoff))\n\n\tv := url.Values{}\n\tv.Set(\"pickup\", pickup)\n\tv.Set(\"dropoff\", dropoff)\n\trouteURL := \"http://\" + c.hostPort + \"/route?\" + v.Encode()\n\tvar route Route\n\tif err := c.client.GetJSON(ctx, \"/route\", routeURL, &route); err != nil {\n\t\tc.logger.For(ctx).Error(\"Error getting route\", zap.Error(err))\n\t\treturn nil, err\n\t}\n\treturn &route, nil\n}\n"
  },
  {
    "path": "examples/hotrod/services/route/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage route\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "examples/hotrod/services/route/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage route\n\nimport (\n\t\"context\"\n\t\"time\"\n)\n\n// Route describes a route between Pickup and Dropoff locations and expected time to arrival.\ntype Route struct {\n\tPickup  string\n\tDropoff string\n\tETA     time.Duration\n}\n\n// Interface exposed by the Driver service.\ntype Interface interface {\n\tFindRoute(ctx context.Context, pickup, dropoff string) (*Route, error)\n}\n"
  },
  {
    "path": "examples/hotrod/services/route/server.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage route\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/delay\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/httperr\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/log\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/services/config\"\n)\n\n// Server implements Route service\ntype Server struct {\n\thostPort string\n\ttracer   trace.TracerProvider\n\tlogger   log.Factory\n}\n\n// NewServer creates a new route.Server\nfunc NewServer(hostPort string, tracer trace.TracerProvider, logger log.Factory) *Server {\n\treturn &Server{\n\t\thostPort: hostPort,\n\t\ttracer:   tracer,\n\t\tlogger:   logger,\n\t}\n}\n\n// Run starts the Route server\nfunc (s *Server) Run() error {\n\tmux := s.createServeMux()\n\ts.logger.Bg().Info(\"Starting\", zap.String(\"address\", \"http://\"+s.hostPort))\n\tserver := &http.Server{\n\t\tAddr:              s.hostPort,\n\t\tHandler:           mux,\n\t\tReadHeaderTimeout: 3 * time.Second,\n\t}\n\treturn server.ListenAndServe()\n}\n\nfunc (s *Server) createServeMux() http.Handler {\n\tmux := tracing.NewServeMux(false, s.tracer, s.logger)\n\tmux.Handle(\"/route\", http.HandlerFunc(s.route))\n\tmux.Handle(\"/debug/vars\", http.HandlerFunc(movedToFrontend))\n\tmux.Handle(\"/metrics\", http.HandlerFunc(movedToFrontend))\n\treturn mux\n}\n\nfunc movedToFrontend(w http.ResponseWriter, _ *http.Request) {\n\thttp.Error(w, \"endpoint moved to the frontend service\", http.StatusNotFound)\n}\n\nfunc (s *Server) route(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\ts.logger.For(ctx).Info(\"HTTP request received\", zap.String(\"method\", r.Method), zap.Stringer(\"url\", r.URL))\n\tif err := r.ParseForm(); httperr.HandleError(w, err, http.StatusBadRequest) {\n\t\ts.logger.For(ctx).Error(\"bad request\", zap.Error(err))\n\t\treturn\n\t}\n\n\tpickup := r.Form.Get(\"pickup\")\n\tif pickup == \"\" {\n\t\thttp.Error(w, \"Missing required 'pickup' parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tdropoff := r.Form.Get(\"dropoff\")\n\tif dropoff == \"\" {\n\t\thttp.Error(w, \"Missing required 'dropoff' parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tresponse := computeRoute(ctx, pickup, dropoff)\n\n\tdata, err := json.Marshal(response)\n\tif httperr.HandleError(w, err, http.StatusInternalServerError) {\n\t\ts.logger.For(ctx).Error(\"cannot marshal response\", zap.Error(err))\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(data) //nolint:gosec // G705 - writing JSON response\n}\n\nfunc computeRoute(ctx context.Context, pickup, dropoff string) *Route {\n\tstart := time.Now()\n\tdefer func() {\n\t\tupdateCalcStats(ctx, time.Since(start))\n\t}()\n\n\t// Simulate expensive calculation\n\tdelay.Sleep(config.RouteCalcDelay, config.RouteCalcDelayStdDev)\n\n\teta := math.Max(2, rand.NormFloat64()*3+5)\n\treturn &Route{\n\t\tPickup:  pickup,\n\t\tDropoff: dropoff,\n\t\tETA:     time.Duration(eta) * time.Minute,\n\t}\n}\n"
  },
  {
    "path": "examples/hotrod/services/route/stats.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage route\n\nimport (\n\t\"context\"\n\t\"expvar\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/examples/hotrod/pkg/tracing\"\n)\n\nvar (\n\trouteCalcByCustomer = expvar.NewMap(\"route.calc.by.customer.sec\")\n\trouteCalcBySession  = expvar.NewMap(\"route.calc.by.session.sec\")\n)\n\nvar stats = []struct {\n\texpvar     *expvar.Map\n\tbaggageKey string\n}{\n\t{\n\t\texpvar:     routeCalcByCustomer,\n\t\tbaggageKey: \"customer\",\n\t},\n\t{\n\t\texpvar:     routeCalcBySession,\n\t\tbaggageKey: \"session\",\n\t},\n}\n\nfunc updateCalcStats(ctx context.Context, delay time.Duration) {\n\tdelaySec := float64(delay/time.Millisecond) / 1000.0\n\tfor _, s := range stats {\n\t\tkey := tracing.BaggageItem(ctx, s.baggageKey)\n\t\tif key != \"\" {\n\t\t\ts.expvar.AddFloat(key, delaySec)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/oci/README.md",
    "content": "# Jaeger + Prometheus + HotROD Demo Setup (Helm v2 Branch)\n\nThis guide walks you through deploying **Jaeger** (using the v2 Helm chart), **Prometheus**, and the **HotROD demo app** on Kubernetes.\n\n## Prerequisites\n\nEnsure the following tools are installed and configured:\n\n- A Kubernetes cluster (e.g., Minikube, kind, or cloud-based)\n- [`kubectl`](https://kubernetes.io/docs/tasks/tools/)\n- [`Helm 3`](https://helm.sh/docs/intro/install/)\n- `git`\n\n---\n\n## Deploy the Jaeger Demo Setup\n\nThe following components are deployed as part of the Jaeger demo setup:\n\n- **Jaeger All-in-One**: Tracing backend (collector, query, UI, agent in one pod)\n- **HotROD Demo App**: Sample microservices application for tracing demonstration\n- **Prometheus Monitoring Stack**: Includes Prometheus, Grafana, and Alertmanager for metrics and dashboards\n- **Load Generator**: Continuously generates traces from the HotROD app\n\nTo deploy the entire infrastructure with a single command, run:\n\n```bash\nbash ./deploy-all.sh\n```\nThis script will automatically install and configure all components on your Kubernetes , To deal with individual components refer to deploy-all.sh script . \n\n## Access the Deployment\n\nAfter deploying, you can access each component locally using the following port-forward commands in separate terminals:\n\n```bash\n# Jaeger UI\nkubectl port-forward svc/jaeger-query 16686:16686\n\n# Prometheus UI\nkubectl port-forward svc/prometheus 9090:9090\n\n# Grafana Dashboard\nkubectl port-forward svc/prometheus-grafana 9091:80\n\n# HotROD UI\nkubectl port-forward svc/jaeger-hotrod 8080:80\n```\n\nThen, open the following URLs in your browser:\n\n- **Jaeger UI:** [http://localhost:16686/jaeger](http://localhost:16686/jaeger)\n- **Prometheus:** [http://localhost:9090](http://localhost:9090)\n- **Grafana:** [http://localhost:9091](http://localhost:9091)\n- **HotROD Demo App:** [http://localhost:8080/hotrod](http://localhost:8080/hotrod)\n\n## Deploying on Cloud Infrastructure (e.g., Oracle Cloud)\n\nTo expose your services externally using a custom domain (e.g., `http://demo.jaegertracing.io/`), you need to set up an **Ingress Controller** and define an **Ingress resource**.\n\n### Step 1: Deploy the NGINX Ingress Controller\n\nApply the official NGINX Ingress Controller manifest for cloud environments:\n\n```bash\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v<VERSION>/deploy/static/provider/cloud/deploy.yaml\n```\n\n> 🔁 Replace `<VERSION>` with the latest version from the [Ingress NGINX GitHub Releases](https://github.com/kubernetes/ingress-nginx/releases).\n\n---\n\n### Step 2: Verify the Ingress Controller\n\nAfter deployment, check that the ingress controller service is up and has an external IP:\n\n```bash\nkubectl get svc -n ingress-nginx\n```\n\nYou should see output like:\n\n```bash\nNAME                       TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                       AGE\ningress-nginx-controller   LoadBalancer   10.96.229.38    129.146.214.219   80:30756/TCP,443:30118/TCP    1h\n```\n\n> 🧠 Note: The `EXTERNAL-IP` is the public IP address your domain (e.g., `demo.jaegertracing.io`) should point to via DNS.\n\n---\n\n### Step 3: Apply the Ingress Resource\n\nOnce your DNS is mapped and the ingress controller is ready, deploy your Ingress definition:\n\n```bash\nkubectl apply -f ingress.yaml\n```\n\nThis routes incoming HTTP traffic to the respective Kubernetes services based on the path or host rules defined in `ingress.yaml`.\n\n---\n\n🔧 Remarks\n\n📌 The current configuration is set to run in the default namespace.\nYou can use any custom namespace by making minor adjustments in:\n``` bash\nHelm --namespace flags\nKubernetes manifests (metadata.namespace)\nPrometheus scrape configs and service selectors if targeting Jaeger in a different namespace\n```\n📌 The default credentials for Grafana dashboards are:\n\n- **Username:** `admin`\n- **Password:** `prom-operator`\n\nOnce logged in, you can explore the pre-built dashboards or add your own tracing and metrics visualizations.\n\n### Configure TLS/SSL with Cert-Manager\n\nTo secure services with TLS/SSL, we use **Cert-Manager**. It provides the following features:\n\n- Automatic provisioning of TLS/SSL certificates.\n- Integration with Let's Encrypt for certificates.\n- Automatic renewal of certificates before expiration.\n- Integration with NGINX Ingress Controller.\n\nThe issuer configuration YAML is located at `./tls-cert/issuer.yaml`.\n\nFor detailed setup instructions, refer to the official Cert-Manager documentation: [Cert-Manager ACME Tutorial with NGINX Ingress](https://cert-manager.io/docs/tutorials/acme/nginx-ingress/)"
  },
  {
    "path": "examples/oci/config.yaml",
    "content": "service:\n  extensions: [jaeger_storage, jaeger_query, healthcheckv2]\n  pipelines:\n    traces:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [jaeger_storage_exporter, spanmetrics]\n    metrics/spanmetrics:\n      receivers: [spanmetrics]\n      exporters: [prometheus]\n  telemetry:\n    resource:\n      service.name: jaeger\n    metrics:\n      level: detailed\n      readers:\n        - pull:\n            exporter:\n              prometheus:\n                host: 0.0.0.0\n                port: 8888\n    logs:\n      level: DEBUG\n\nextensions:\n  healthcheckv2:\n      use_v2: true\n      http:\n        endpoint: 0.0.0.0:13133\n  jaeger_query:\n    base_path: /jaeger\n    ui:\n      config_file: /etc/jaeger/ui-config/ui-config.json\n      log_access: true\n    storage:\n      traces: some_storage\n      metrics: some_metrics_storage\n  jaeger_storage:\n    backends:\n      some_storage:\n        memory:\n          max_traces: 100000\n    metric_backends:\n      some_metrics_storage:\n        prometheus:\n          endpoint: http://prometheus:9090\n          normalize_calls: true\n          normalize_duration: true\n\nconnectors:\n  spanmetrics:\n\nreceivers:\n  otlp:\n    protocols:\n      grpc:\n      http:\n        endpoint: \"0.0.0.0:4318\"\n\nprocessors:\n  batch:\n\nexporters:\n  jaeger_storage_exporter:\n    trace_storage: some_storage\n  prometheus:\n    endpoint: \"0.0.0.0:8889\""
  },
  {
    "path": "examples/oci/deploy-all.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euo pipefail\n\nMODE=\"${1:-upgrade}\"\nIMAGE_TAG=\"${2:-latest}\"\nPROMETHEUS_STACK_CHART=\"prometheus-community/kube-prometheus-stack\"\nPROMETHEUS_STACK_CHART_VERSION=\"${PROMETHEUS_STACK_CHART_VERSION:-82.10.4}\"\n\ncase \"$MODE\" in\n  upgrade|clean|local)\n    echo \"🔵 Running in '$MODE' mode...\"\n    ;;\n  *)\n    echo \"❌ Error: Invalid mode '$MODE'\"\n    echo \"Usage: $0 [upgrade|clean|local] [image-tag]\"\n    echo \"\"\n    echo \"Modes:\"\n    echo \"  upgrade  - Upgrade existing deployment or install if not present (default)\"\n    echo \"  clean    - Clean install (removes existing deployment first)\"\n    echo \"  local    - Deploy using local registry images (localhost:5000)\"\n    echo \"\"\n    echo \"Examples:\"\n    echo \"  $0                    # Upgrade mode with latest tag\"\n    echo \"  $0 clean              # Clean install\"\n    echo \"  $0 local <image_tag>       # Local mode with specific image tag\"\n    exit 1\n    ;;\nesac\n\nif [[ \"$MODE\" == \"upgrade\" ]]; then\n  HELM_JAEGER_CMD=\"upgrade --install --force\"\n  HELM_PROM_CMD=\"upgrade --install --force\"\nelse\n  echo \"🟣 Clean mode: Uninstalling Jaeger and Prometheus...\"\n  helm uninstall jaeger --ignore-not-found || true\n  helm uninstall prometheus --ignore-not-found || true\n  for name in jaeger prometheus; do\n    while helm list --filter \"^${name}$\" | grep \"$name\" &>/dev/null; do\n      echo \"Waiting for Helm release $name to be deleted...\"\n    done\n  done\n  HELM_JAEGER_CMD=\"install\"\n  HELM_PROM_CMD=\"install\"\nfi\n\n# Navigate to the script's directory (examples/oci)\ncd $(dirname $0)\n\n# Clone Jaeger Helm Charts if not already present\nif [ ! -d \"helm-charts\" ]; then\n  echo \"📥 Cloning Jaeger Helm Charts...\"\n  git clone https://github.com/jaegertracing/helm-charts.git\n  cd helm-charts\n  echo \"Using v2 branch for Jaeger v2...\"\n  git checkout v2\n  echo \"Adding required Helm repositories...\"\n  helm repo add bitnami https://charts.bitnami.com/bitnami\n  helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n  helm repo add incubator https://charts.helm.sh/incubator\n  helm repo update\n  helm dependency build ./charts/jaeger\n  cd ..\nelse\n  echo \"📁 Jaeger Helm Charts already exist. Skipping clone.\"\nfi\n\n# Set image repositories and deploy based on mode\nif [[ \"$MODE\" == \"local\" ]]; then\n\n  echo \"🟣 Deploying Jaeger with local registry images...\"\n  helm $HELM_JAEGER_CMD --timeout 10m0s jaeger ./helm-charts/charts/jaeger \\\n    --set provisionDataStore.cassandra=false \\\n    --set allInOne.enabled=true \\\n    --set storage.type=memory \\\n    --set hotrod.enabled=true \\\n    --set global.imageRegistry=\"\" \\\n    --set allInOne.image.repository=\"localhost:5000/jaegertracing/jaeger\" \\\n    --set allInOne.image.tag=\"${IMAGE_TAG}\"  \\\n    --set allInOne.image.pullPolicy=\"Never\" \\\n    --set hotrod.image.repository=\"localhost:5000/jaegertracing/example-hotrod\" \\\n    --set hotrod.image.tag=\"${IMAGE_TAG}\"  \\\n    --set hotrod.image.pullPolicy=\"Never\" \\\n    --set-file userconfig=\"./config.yaml\" \\\n    --set-file uiconfig=\"./ui-config.json\" \\\n    -f ./jaeger-values.yaml\nelse\n  echo \"🟣 Deploying Jaeger...\"\n  helm $HELM_JAEGER_CMD --timeout 10m0s jaeger ./helm-charts/charts/jaeger \\\n    --set provisionDataStore.cassandra=false \\\n    --set allInOne.enabled=true \\\n    --set storage.type=memory \\\n    --set allInOne.image.repository=\"jaegertracing/jaeger\" \\\n    --set-file userconfig=\"./config.yaml\" \\\n    --set-file uiconfig=\"./ui-config.json\" \\\n    -f ./jaeger-values.yaml\nfi\n\necho \"🟢 Deploying Prometheus...\"\nkubectl apply -f prometheus-svc.yaml\nhelm $HELM_PROM_CMD prometheus \"$PROMETHEUS_STACK_CHART\" \\\n  --version \"$PROMETHEUS_STACK_CHART_VERSION\" \\\n  --set crds.upgradeJob.enabled=true \\\n  --set crds.upgradeJob.forceConflicts=true \\\n  -f monitoring-values.yaml\n\n# Create ConfigMap for Trace Generator\necho \"🔵 Step 3: Creating ConfigMap for Trace Generator...\"\nkubectl create configmap trace-script --from-file=./load-generator/generate_traces.py --dry-run=client -o yaml | kubectl apply -f -\n\n# Deploy Trace Generator Pod\necho \"🟡 Step 4: Deploying Trace Generator Pod...\"\nkubectl apply -f ./load-generator/load-generator.yaml\n\n# Deploy ingress changes \necho \"🟡 Step 5: Deploying Ingress Resource...\"\nkubectl apply -f ingress.yaml\n\n# Output Port-forward Instructions\necho \"✅ Deployment Complete!\"\necho \"\"\necho \"📡 Port-forward the following to access UIs locally:\"\necho \"\"\necho \"kubectl port-forward svc/jaeger-query 16686:16686      # Jaeger UI\"\necho \"kubectl port-forward svc/prometheus 9090:9090          # Prometheus UI\"\necho \"kubectl port-forward svc/prometheus-grafana 9091:80    # Grafana UI\"\necho \"kubectl port-forward svc/jaeger-hotrod 8080:80         # HotROD UI\"\necho \"\"\necho \"Then open:\"\necho \"🔍 Jaeger: http://localhost:16686/jaeger\"\necho \"📈 Prometheus: http://localhost:9090\"\necho \"📊 Grafana: http://localhost:9091\"\necho \"🚕 HotROD: http://localhost:8080\"\necho \"\"\necho \"📝 Note: If you made changes to Jaeger configuration files (e.g., config.yaml, ui-config.json), you may need to run this script in clean mode:\"\necho \"    ./deploy-all.sh clean\"\necho \"Or manually restart the CI workflow to ensure your changes are applied.\"\n"
  },
  {
    "path": "examples/oci/ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: jaeger-demo-ingress\n  annotations:\n    cert-manager.io/issuer: letsencrypt-prod\nspec:\n  ingressClassName: nginx\n  tls:\n    - hosts:\n        - demo.jaegertracing.io\n      secretName: demo-jaeger-tls\n  rules:\n    - host: demo.jaegertracing.io\n      http:\n        paths:\n          - path: /jaeger\n            pathType: Prefix\n            backend:\n              service:\n                name: jaeger-query\n                port:\n                  number: 16686\n          - path: /grafana\n            pathType: Prefix\n            backend:\n              service:\n                name: prometheus-grafana\n                port:\n                  number: 80\n          - path: /hotrod\n            pathType: Prefix\n            backend:\n              service:\n                name: jaeger-hotrod\n                port:\n                  number: 80\n\n---\n# Separate Ingress to redirect / -> external site\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: jaeger-root-redirect\n  annotations:\n    cert-manager.io/issuer: letsencrypt-prod\n    nginx.ingress.kubernetes.io/permanent-redirect: https://www.jaegertracing.io/demo/\nspec:\n  ingressClassName: nginx\n  tls:\n    - hosts:\n        - demo.jaegertracing.io\n      secretName: demo-jaeger-tls\n  rules:\n    - host: demo.jaegertracing.io\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              # Backend is ignored due to redirect annotation; must be valid\n              service:\n                name: jaeger-query\n                port:\n                  number: 16686"
  },
  {
    "path": "examples/oci/jaeger-values.yaml",
    "content": "global:\n  imageRegistry: docker.io\nhotrod:\n  enabled: true\n  image:\n    repository: jaegertracing/example-hotrod\n    tag: \"1.72.0\"\n  args:\n    - all\n  extraArgs:\n    - --otel-exporter=otlp\n    - --basepath=/hotrod\n    - --jaeger-ui=https://demo.jaegertracing.io/jaeger\n  livenessProbe:\n    path: /hotrod\n  readinessProbe: \n    path: /hotrod\n  extraEnv:\n    - name: OTEL_EXPORTER_OTLP_ENDPOINT\n      value: http://jaeger-collector:4318\n    - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT\n      value: http://jaeger-collector:4318/v1/traces\n    - name: OTEL_EXPORTER_OTLP_PROTOCOL\n      value: http/protobuf\n    - name: OTEL_SERVICE_NAME\n      value: hotrod\n    - name: OTEL_LOG_LEVEL\n      value: debug"
  },
  {
    "path": "examples/oci/load-generator/generate_traces.py",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport requests\nimport random\nimport time\n\nurl = \"http://jaeger-hotrod.default.svc.cluster.local:80/hotrod/dispatch\" # /hotrod is the basepath\n\ncustomer_ids = [123, 392, 731, 567]\ni = 0\nprint(\"Starting load generator script\")\nwhile True:  #Keep sending requests\n    customer = random.choice(customer_ids)\n    nonse = random.random()\n    params = {\n        \"customer\": customer,\n        \"nonse\": nonse\n    }\n\n    try:\n        res = requests.get(url, params=params, timeout=5)\n        print(f\"[{i}]th request Sent to {res.url} → Status: {res.status_code}\")\n    except Exception as e:\n        print(f\"[{i}]th request Error: {e}\")\n    i = i + 1\n    time.sleep(10)  # Pause between requests to avoid overload\n"
  },
  {
    "path": "examples/oci/load-generator/load-generator.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: trace-generator\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: trace-generator\n  template:\n    metadata:\n      labels:\n        app: trace-generator\n    spec:\n      containers:\n      - name: trace-generator\n        image: python:3.11\n        command: \n          - /bin/sh\n          - -c\n          - |\n            pip install requests && python /app/generate_traces.py\n        volumeMounts:\n        - name: script-volume\n          mountPath: /app\n      restartPolicy: Always\n      volumes:\n      - name: script-volume\n        configMap:\n          name: trace-script\n"
  },
  {
    "path": "examples/oci/monitoring-values.yaml",
    "content": "# Configuration for Prometheus , Grafana , Alertmanager can be set from this configuration \n\nfullnameOverride: \"\"\nprometheus:\n  prometheusSpec:\n    enableAdminAPI: true\n    additionalScrapeConfigs: |\n      - job_name: aggregated-trace-metrics\n        static_configs:\n          - targets: ['jaeger-collector-prometheus.default.svc.cluster.local:8889']\n        scrape_interval:     15s\n\n\ngrafana:\n  grafana.ini:\n    server:\n      domain: demo.jaegertracing.io\n      root_url: \"%(protocol)s://%(domain)s/grafana/\"\n      serve_from_sub_path: true"
  },
  {
    "path": "examples/oci/prometheus-svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: prometheus\n  labels:\n    app.kubernetes.io/name: prometheus\nspec:\n  selector:\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/instance: prometheus-kube-prometheus-prometheus\n  ports:\n    - name: http\n      port: 9090\n      targetPort: 9090\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: jaeger-collector-prometheus\nspec:\n  selector:\n    app.kubernetes.io/name: jaeger\n    app.kubernetes.io/instance: jaeger\n  ports:\n    - name: prometheus\n      port: 8889\n      targetPort: 8889\n    - name: metrics\n      port: 8888\n      targetPort: 8888"
  },
  {
    "path": "examples/oci/tls-cert/issuer.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: letsencrypt-prod\nspec:\n  acme:\n    # The ACME server URL\n    server: https://acme-v02.api.letsencrypt.org/directory\n    # Email address used for ACME registration\n    email: cncf-jaeger-maintainers@lists.cncf.io\n    # The ACME certificate profile\n    profile: tlsserver\n    # Name of a secret used to store the ACME account private key\n    privateKeySecretRef:\n      name: letsencrypt-prod\n    # Enable the HTTP-01 challenge provider\n    solvers:\n      - http01:\n          ingress:\n            ingressClassName: nginx"
  },
  {
    "path": "examples/oci/ui-config.json",
    "content": "{\n  \"tracking\": {\n    \"gaID\": \"G-S88L6V684T\",\n    \"trackErrors\": true\n  }\n}\n"
  },
  {
    "path": "examples/opentracing-tutorial/README.md",
    "content": "# OpenTracing Instrumentation Tutorial\n\nTo learn how to instrument your own applications for distributed tracing with Jaeger and OpenTracing API, \nplease see https://github.com/yurishkuro/opentracing-tutorial/.\n\n"
  },
  {
    "path": "examples/otel-demo/README.md",
    "content": "# OpenTelemetry Demo app + HotRODapp + Jaeger + OpenSearch \n\nThis example provides a one-command deployment of a complete observability stack on Kubernetes:\n- Jaeger (all-in-one) for tracing\n- OpenSearch and OpenSearch Dashboards\n- OpenTelemetry Demo application (multi-service web store)\n- HotRod application\n\nIt is driven by `deploy-all.sh`, which supports both clean installs and upgrades.\n\n## Prerequisites\n- Kubernetes cluster reachable via `kubectl`\n- Installed CLIs: `bash`, `git`, `curl`, `kubectl`, `helm`\n- Network access to Helm repositories\n\n## Quick start\n- Clean install (removes previous releases/namespaces, then installs everything):\n```bash path=null start=null\n./deploy-all.sh clean\n```\n- Upgrade (default) — installs if missing, upgrades if present:\n```bash path=null start=null\n./deploy-all.sh\n# or explicitly\n./deploy-all.sh upgrade\n```\n- Specify Jaeger all-in-one image tag:\n```bash path=null start=null\n./deploy-all.sh upgrade <image-tag>\n# Example\n./deploy-all.sh upgrade latest\n```\n\nEnvironment variables:\n- ROLLOUT_TIMEOUT: rollout wait timeout in seconds (default 600)\n\n```bash path=null start=null\nROLLOUT_TIMEOUT=900 ./deploy-all.sh clean\n```\n\n## What gets deployed\n- Namespace `opensearch`:\n  - OpenSearch (single node) StatefulSet\n  - OpenSearch Dashboards Deployment\n- Namespace `jaeger`:\n  - Jaeger all-in-one Deployment (storage=none)\n  - HOTROD application \n  - Jaeger Query ClusterIP service (jaeger-query-clusterip)\n- Namespace `otel-demo`:\n  - OpenTelemetry Demo (frontend, load-generator, and supporting services)\n  \n\n## Verifying the deployment\n- Pods status:\n```bash path=null start=null\nkubectl get pods -n opensearch\nkubectl get pods -n jaeger\nkubectl get pods -n otel-demo\n```\n- Services:\n```bash path=null start=null\nkubectl get svc -n opensearch\nkubectl get svc -n jaeger\nkubectl get svc -n otel-demo\n```\n\n\n## Automatic port-forward using scrpit\n - OpenSearch Dashboards:\n```bash path=null start=null\n./start-port-forward.sh\n\n\n## Customization\n- Helm values provided in this directory:\n  - `opensearch-values.yaml`\n  - `opensearch-dashboard-values.yaml`\n  - `jaeger-values.yaml`\n  - `jaeger-config.yaml`\n  - `otel-demo-values.yaml`\n  - `jaeger-query-service.yaml`\n\nYou can adjust these files and re-run `./deploy-all.sh upgrade` to apply changes.\n\n## Clean-up\n- Clean uninstall using cleanup.sh :\n```bash path=null start=null\n./cleanup.sh\n```\n- Manual teardown:\n```bash path=null start=null\nhelm uninstall opensearch -n opensearch || true\nhelm uninstall opensearch-dashboards -n opensearch || true\nhelm uninstall jaeger -n jaeger || true\nhelm uninstall otel-demo -n otel-demo || true\nkubectl delete namespace opensearch jaeger otel-demo --ignore-not-found=true\n```\n\n\n\n"
  },
  {
    "path": "examples/otel-demo/cleanup.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# OpenSearch Observability Stack Cleanup Script \n\nmain() {\n  echo \"Starting OpenSearch Observability Stack Cleanup\"\n\n  # Stop any existing port forwards\n  echo \"Stopping any existing port-forward processes...\"\n  pkill -f \"kubectl port-forward\" 2>/dev/null || true\n  echo \"✅ Port-forward processes stopped\"\n\n  # Uninstall OTEL Demo\n  echo \" Uninstalling OTEL Demo...\"\n  if helm list -n otel-demo | grep -q otel-demo; then\n    helm uninstall otel-demo -n otel-demo\n    echo \"✅ OTEL Demo uninstalled\"\n  else\n    echo \"⚠️ OTEL Demo not found or already uninstalled\"\n  fi\n\n  # Uninstall Jaeger\n  echo \"Uninstalling Jaeger...\"\n  if helm list -n jaeger | grep -q jaeger; then\n    helm uninstall jaeger -n jaeger\n    echo \"✅ Jaeger uninstalled\"\n  else\n    echo \"⚠️ Jaeger not found or already uninstalled\"\n  fi\n\n  # Uninstall OpenSearch Dashboards\n  echo \"Uninstalling OpenSearch Dashboards...\"\n  if helm list -n opensearch | grep -q opensearch-dashboards; then\n    helm uninstall opensearch-dashboards -n opensearch\n    echo \"✅ OpenSearch Dashboards uninstalled\"\n  else\n    echo \"⚠️ OpenSearch Dashboards not found or already uninstalled\"\n  fi\n\n  # Uninstall OpenSearch\n  echo \" Uninstalling OpenSearch...\"\n  if helm list -n opensearch | grep -q opensearch; then\n    helm uninstall opensearch -n opensearch\n    echo \"✅ OpenSearch uninstalled\"\n  else\n    echo \"⚠️ OpenSearch not found or already uninstalled\"\n  fi\n\n  # Wait for pods to terminate\n  echo \"Waiting for pods to terminate...\"\n  sleep 10\n\n  # Delete namespaces\n  echo \"Deleting namespaces...\"\n  for ns in otel-demo jaeger opensearch; do\n    if kubectl get namespace \"$ns\" > /dev/null 2>&1; then\n      kubectl delete namespace \"$ns\" --force --grace-period=0 2>/dev/null || true\n      echo \"✅ Namespace $ns deleted\"\n    else\n      echo \"⚠️ Namespace $ns not found or already deleted\"\n    fi\n  done\n\n  # Clean up any remaining resources (PVCs, etc.)\n  echo \"Cleaning up any remaining PVCs...\"\n  kubectl get pvc -A | grep -E \"(opensearch|jaeger|otel-demo)\" || echo \"No remaining PVCs found\"\n\n  # Final verification\n  echo \"Performing final verification...\"\n  remaining_pods=$(kubectl get pods -A | grep -E \"(opensearch|jaeger|otel-demo)\" || true)\n  if [ -z \"$remaining_pods\" ]; then\n    echo \"All components cleaned up successfully!\"\n  else\n    echo \"⚠️ Some pods may still be terminating:\"\n    echo \"$remaining_pods\"\n    echo \"This is normal and they should disappear shortly\"\n  fi\n\n  echo \"\"\n  echo \"✅ Cleanup Complete!\"\n  echo \"\"\n  echo \" All OpenSearch observability stack components have been removed\"\n  echo \"\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "examples/otel-demo/deploy-all.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nROLLOUT_TIMEOUT=\"${ROLLOUT_TIMEOUT:-600}\"\n\nMODE=\"${1:-upgrade}\"\nIMAGE_TAG=\"${2:-latest}\"\n\ncase \"$MODE\" in\n  upgrade|clean)\n    echo \" Running in '$MODE' mode...\"\n    ;;\n  *)\n    echo \"Error: Invalid mode '$MODE'\"\n    echo \"Usage: $0 [upgrade|clean] [image-tag]\"\n    echo \"\"\n    echo \"Modes:\"\n    echo \"  upgrade  - Upgrade existing deployment or install if not present (default)\"\n    echo \"  clean    - Clean install (removes existing deployment first)\"\n    echo \"\"\n    echo \"Examples:\"\n    echo \"  $0                    # Upgrade mode with latest tag\"\n    echo \"  $0 clean              # Clean install\"\n    exit 1\n    ;;\n esac\n\nif [[ \"$MODE\" == \"upgrade\" ]]; then\n  HELM_JAEGER_CMD=\"upgrade --install --force\"\nelse\n  # For clean mode, use install after cleanup\n  HELM_JAEGER_CMD=\"install\"\nfi\n\nlog() { echo \"[$(date +\"%F %T\")] $*\"; }\nerr() { echo \"[$(date +\"%F %T\")] ERROR: $*\" >&2; exit 1; }\n\nneed() {\n  if ! command -v \"$1\" >/dev/null 2>&1; then\n    err \"$1 is required but not installed\"\n  fi\n}\n\ncheck_cluster() {\n  if ! kubectl cluster-info >/dev/null 2>&1; then\n    err \"Cannot reach a Kubernetes cluster with kubectl\"\n  fi\n}\n\ncheck_required_files() {\n  local files=(\n    \"$SCRIPT_DIR/opensearch-values.yaml\"\n    \"$SCRIPT_DIR/opensearch-dashboard-values.yaml\"\n    \"$SCRIPT_DIR/jaeger-values.yaml\"\n    \"$SCRIPT_DIR/jaeger-config.yaml\"\n    \"$SCRIPT_DIR/otel-demo-values.yaml\"\n    \"$SCRIPT_DIR/jaeger-query-service.yaml\"\n  )\n  for f in \"${files[@]}\"; do\n    [[ -f \"$f\" ]] || err \"Missing required file: $f\"\n  done\n}\n\nwait_for_deployment() {\n  local namespace=\"$1\"\n  local deployment=\"$2\"\n  local timeout=\"${3:-${ROLLOUT_TIMEOUT}s}\"\n  log \"Waiting for deployment $deployment in $namespace...\"\n  if ! kubectl rollout status \"deployment/$deployment\" -n \"$namespace\" --timeout=\"$timeout\"; then\n    kubectl -n \"$namespace\" get deploy \"$deployment\" -o wide || true\n    kubectl -n \"$namespace\" describe deploy \"$deployment\" || true\n    kubectl -n \"$namespace\" get pods -l app.kubernetes.io/name=\"$deployment\" -o wide || true\n    err \"Deployment $deployment failed to become ready in $namespace\"\n  fi\n  log \"Deployment $deployment is ready\"\n}\n\nwait_for_statefulset() {\n  local namespace=\"$1\"\n  local sts=\"$2\"\n  local timeout=\"${3:-${ROLLOUT_TIMEOUT}s}\"\n  log \"Waiting for statefulset $sts in $namespace...\"\n  if ! kubectl rollout status \"statefulset/$sts\" -n \"$namespace\" --timeout=\"$timeout\"; then\n    kubectl -n \"$namespace\" get statefulset \"$sts\" -o wide || true\n    kubectl -n \"$namespace\" describe statefulset \"$sts\" || true\n    kubectl -n \"$namespace\" get pods -l statefulset.kubernetes.io/pod-name -o wide || true\n    err \"StatefulSet $sts failed to become ready in $namespace\"\n  fi\n  log \"StatefulSet $sts is ready\"\n}\n\nwait_for_service_endpoints() {\n  local namespace=\"$1\"\n  local service=\"$2\"\n  local timeout_secs=\"${3:-120}\"\n  log \"Waiting for service $service endpoints in $namespace...\"\n  for i in $(seq 1 \"$timeout_secs\"); do\n    if kubectl get endpoints \"$service\" -n \"$namespace\" >/dev/null 2>&1; then\n      local ready\n      ready=$(kubectl get endpoints \"$service\" -n \"$namespace\" -o jsonpath='{.subsets[*].addresses[*].ip}' 2>/dev/null || true)\n      if [[ -n \"$ready\" ]]; then\n        log \"Service $service has endpoints: $ready\"\n        return 0\n      fi\n    fi\n    sleep 1\n  done\n  kubectl get svc \"$service\" -n \"$namespace\" -o wide || true\n  kubectl get endpoints \"$service\" -n \"$namespace\" -o yaml || true\n  err \"Service $service in $namespace has no ready endpoints after ${timeout_secs}s\"\n}\n\ncleanup() {\n  log \"Cleanup: uninstalling existing releases if present\"\n  helm uninstall opensearch -n opensearch >/dev/null 2>&1 || true\n  helm uninstall opensearch-dashboards -n opensearch >/dev/null 2>&1 || true\n  helm uninstall jaeger -n jaeger >/dev/null 2>&1 || true\n  helm uninstall otel-demo -n otel-demo >/dev/null 2>&1 || true\n\n  log \"Cleanup: deleting ingress resources\"\n  kubectl delete ingress --all -n jaeger >/dev/null 2>&1 || true\n  kubectl delete ingress --all -n opensearch >/dev/null 2>&1 || true\n  kubectl delete ingress --all -n otel-demo >/dev/null 2>&1 || true\n\n  log \"Cleanup: deleting namespaces (may take time)\"\n  for ns in jaeger otel-demo opensearch; do\n    kubectl delete namespace \"$ns\" --ignore-not-found=true >/dev/null 2>&1 || true\n  done\n\n  # Wait for namespaces to disappear\n  for ns in jaeger otel-demo opensearch; do\n    for i in {1..120}; do\n      if ! kubectl get namespace \"$ns\" >/dev/null 2>&1; then\n        break\n      fi\n      sleep 2\n    done\n  done\n  log \"Cleanup complete\"\n}\n\n# Deploy HTTPS ingress resources\ndeploy_ingress() {\n  log \"Deploying HTTPS ingress resources...\"\n  \n  # Check if ingress files exist\n  if [[ ! -f \"$SCRIPT_DIR/ingress/ingress-jaeger.yaml\" ]]; then\n    log \" Ingress files not found in $SCRIPT_DIR/ingress/ - skipping HTTPS setup\"\n    return 0\n  fi\n  \n  # Apply ingress for each namespace\n  if kubectl apply -f \"$SCRIPT_DIR/ingress/ingress-jaeger.yaml\" 2>&1 | grep -q \"created\\|configured\\|unchanged\"; then\n    log \"Jaeger ingress configured (jaeger.demo.jaegertracing.io, hotrod.demo.jaegertracing.io)\"\n  else\n    log \" Failed to apply Jaeger ingress \"\n  fi\n  \n  if kubectl apply -f \"$SCRIPT_DIR/ingress/ingress-opensearch.yaml\" 2>&1 | grep -q \"created\\|configured\\|unchanged\"; then\n    log \" OpenSearch ingress configured (opensearch.demo.jaegertracing.io)\"\n  else\n    log \"  Failed to apply OpenSearch ingress \"\n  fi\n  \n  if kubectl apply -f \"$SCRIPT_DIR/ingress/ingress-otel-demo.yaml\" 2>&1 | grep -q \"created\\|configured\\|unchanged\"; then\n    log \" OTel Demo ingress configured (shop.demo.jaegertracing.io)\"\n  else\n    log \"  Failed to apply OTel Demo ingress \"\n  fi\n  \n  log \"Waiting for SSL certificates to be issued...\"\n  sleep 10\n  \n  # Check certificate status\n  local certs_ready=0\n  local certs_total=0\n  \n  for ns in jaeger opensearch otel-demo; do\n    if kubectl get namespace \"$ns\" >/dev/null 2>&1; then\n      if kubectl get certificate -n \"$ns\" >/dev/null 2>&1; then\n        certs_total=$((certs_total + 1))\n        if kubectl get certificate -n \"$ns\" -o jsonpath='{.items[*].status.conditions[?(@.type==\"Ready\")].status}' 2>/dev/null | grep -q \"True\"; then\n          certs_ready=$((certs_ready + 1))\n        fi\n      fi\n    fi\n  done\n  \n  if [[ $certs_total -eq 0 ]]; then\n    log \" No certificates found - cert-manager may not be installed\"\n  elif [[ $certs_ready -eq $certs_total ]]; then\n    log \"All SSL certificates ready ($certs_ready/$certs_total)\"\n  else\n    log \"Some certificates still pending ($certs_ready/$certs_total ready)\"\n    log \"Certificates will be issued automatically by cert-manager\"\n  fi\n  \n  log \"HTTPS endpoints:\"\n  log \" • https://jaeger.demo.jaegertracing.io\"\n  log \" • https://hotrod.demo.jaegertracing.io\"\n  log \" • https://opensearch.demo.jaegertracing.io\"\n  log \" • https://shop.demo.jaegertracing.io\"\n}\n\n# Clone Jaeger Helm chart and prepare dependencies\nclone_jaeger_v2() {\n  local dest=\"$SCRIPT_DIR/helm-charts\"\n  if [[ ! -d \"$dest\" ]]; then\n    log \"Cloning Jaeger Helm Charts...\"\n    git clone https://github.com/jaegertracing/helm-charts.git \"$dest\"\n    (\n      cd \"$dest\"\n      log \"Using v2 branch for Jaeger v2...\"\n      git checkout v2\n      log \"Adding required Helm repositories...\"\n      helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null 2>&1 || true\n      helm repo add prometheus-community https://prometheus-community.github.io/helm-charts >/dev/null 2>&1 || true\n      helm repo add incubator https://charts.helm.sh/incubator >/dev/null 2>&1 || true\n      helm repo update >/dev/null\n      helm dependency build ./charts/jaeger\n    )\n  else\n    log \"Jaeger Helm Charts already exist. Skipping clone.\"\n    # Ensure required repos exist even if charts folder already exists\n    helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null 2>&1 || true\n    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts >/dev/null 2>&1 || true\n    helm repo add incubator https://charts.helm.sh/incubator >/dev/null 2>&1 || true\n    helm repo update >/dev/null\n  fi\n}\n\n\n\nmain() {\n  log \"Starting CI deploy (weekly refresh)\"\n  need bash\n  need git\n  need curl\n  need kubectl\n  need helm\n  check_required_files\n  check_cluster\n\n\n  if [[ \"$MODE\" == \"clean\" ]]; then\n    cleanup\n  fi\n\n  log \"Adding/updating Helm repos\"\n  helm repo add opensearch https://opensearch-project.github.io/helm-charts >/dev/null 2>&1 || true\n  helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts >/dev/null 2>&1 || true\n  helm repo add jaegertracing https://jaegertracing.github.io/helm-charts >/dev/null 2>&1 || true\n  helm repo update >/dev/null\n  clone_jaeger_v2\n\n  log \"Deploying OpenSearch\"\n  helm upgrade --install opensearch opensearch/opensearch \\\n    --namespace opensearch --create-namespace \\\n    --version 2.19.0 \\\n    --set image.tag=2.11.0 \\\n    -f \"$SCRIPT_DIR/opensearch-values.yaml\" \\\n    --wait --timeout 10m\n  wait_for_statefulset opensearch opensearch-cluster-single \"${ROLLOUT_TIMEOUT}s\"\n\n  log \"Deploying OpenSearch Dashboards\"\n  helm upgrade --install opensearch-dashboards opensearch/opensearch-dashboards \\\n    --namespace opensearch \\\n    -f \"$SCRIPT_DIR/opensearch-dashboard-values.yaml\" \\\n    --wait --timeout 10m\n  wait_for_deployment opensearch opensearch-dashboards \"${ROLLOUT_TIMEOUT}s\"\n\n  \n  log \"Deploying Jaeger (all-in-one, no storage)\"\n  helm $HELM_JAEGER_CMD jaeger \"$SCRIPT_DIR/helm-charts/charts/jaeger\" \\\n    --namespace jaeger --create-namespace \\\n    --set allInOne.enabled=true \\\n    --set storage.type=none \\\n    --set allInOne.image.repository=jaegertracing/jaeger \\\n    --set allInOne.image.tag=\"${IMAGE_TAG}\" \\\n    --set-file userconfig=\"$SCRIPT_DIR/jaeger-config.yaml\" \\\n    -f \"$SCRIPT_DIR/jaeger-values.yaml\" \\\n    --wait --timeout 10m\n  wait_for_deployment jaeger jaeger \"${ROLLOUT_TIMEOUT}s\"\n\n  \n  log \"Creating Jaeger query ClusterIP service...\"\n  kubectl apply -n jaeger -f \"$SCRIPT_DIR/jaeger-query-service.yaml\"\n  log \"Jaeger query ClusterIP service created\"\n\n  log \"Ensuring Jaeger Collector service endpoints are ready before deploying the demo\"\n  wait_for_service_endpoints jaeger jaeger-collector 180\n\n  log \"Ensuring HotROD service endpoints are ready\"\n  wait_for_service_endpoints jaeger jaeger-hotrod 180\n\n  log \"Deploying HotROD trace generator\"\n  kubectl -n jaeger create configmap trace-script --from-file=\"$SCRIPT_DIR/generate_traces.py\" --dry-run=client -o yaml | kubectl apply -f -\n  kubectl apply -n jaeger -f \"$SCRIPT_DIR/load-generator.yaml\"\n  wait_for_deployment jaeger trace-generator \"${ROLLOUT_TIMEOUT}s\"\n\n  log \"Deploying OpenTelemetry Demo (with in-cluster Collector)\"\n  helm upgrade --install otel-demo open-telemetry/opentelemetry-demo \\\n    -f \"$SCRIPT_DIR/otel-demo-values.yaml\" \\\n    --namespace otel-demo --create-namespace \\\n    --wait --timeout 15m\n  wait_for_deployment otel-demo otel-collector \"${ROLLOUT_TIMEOUT}s\"\n  wait_for_deployment otel-demo frontend \"${ROLLOUT_TIMEOUT}s\"\n  wait_for_deployment otel-demo load-generator \"${ROLLOUT_TIMEOUT}s\"\n\n  log \"All components deployed successfully\"\n\n  # Deploy HTTPS ingress\n  deploy_ingress\n\n  log \"🎉 Deployment complete! Stack is ready.\"\n\n}\n\nmain"
  },
  {
    "path": "examples/otel-demo/generate_traces.py",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport os\nimport requests\nimport random\nimport time\n\nTARGET_URL = os.getenv(\"TARGET_URL\", \"http://jaeger-hotrod.jaeger.svc.cluster.local/dispatch\")\nSLEEP_SECONDS = float(os.getenv(\"SLEEP_SECONDS\", \"5\"))\n\nCUSTOMER_IDS = [123, 392, 731, 567]\n\nprint(f\"Starting HotROD load generator → {TARGET_URL} (interval={SLEEP_SECONDS}s)\")\n\ni = 0\nsession = requests.Session()\n\nwhile True:\n    customer = random.choice(CUSTOMER_IDS)\n    nonse = random.random()\n    params = {\n        \"customer\": customer,\n        \"nonse\": nonse,\n    }\n    try:\n        res = session.get(TARGET_URL, params=params, timeout=5)\n        print(f\"[{i}] Sent to {res.url} → {res.status_code}\")\n    except Exception as e:\n        print(f\"[{i}] Error: {e}\")\n    i += 1\n    time.sleep(SLEEP_SECONDS)\n"
  },
  {
    "path": "examples/otel-demo/ingress/README.md",
    "content": "# Ingress Configuration for OpenTelemetry Demo Stack\n\nThis directory contains the HTTPS ingress configurations for exposing the observability stack services via NGINX ingress controller with Let's Encrypt SSL certificates.\n\n\n\n## Files\n\n- **`clusterissuer-letsencrypt-prod.yaml`** - Let's Encrypt certificate issuer (already deployed)\n- **`ingress-jaeger.yaml`** - Exposes Jaeger UI and HotROD demo\n- **`ingress-opensearch.yaml`** - Exposes OpenSearch Dashboards\n- **`ingress-otel-demo.yaml`** - Exposes OTel Demo Shop (frontend-proxy)\n\n## Exposed Services\n\n| Service | URL | Backend Service | Port |\n|---------|-----|-----------------|------|\n| Jaeger UI | https://jaeger.demo.jaegertracing.io | jaeger-query-clusterip | 16686 |\n| HotROD Demo | https://hotrod.demo.jaegertracing.io | jaeger-hotrod | 80 |\n| OpenSearch Dashboards | https://opensearch.demo.jaegertracing.io | opensearch-dashboards | 5601 |\n| OTel Demo Shop | https://shop.demo.jaegertracing.io | frontend-proxy | 8080 |\n| Load Generator | https://shop.demo.jaegertracing.io/loadgen/ | (via frontend-proxy) | 8080 |\n\n## Certificate Management\n\nCertificates are automatically managed by cert-manager using the Let's Encrypt production issuer.\n\n### View Certificate Status\n```bash\nkubectl get certificates --all-namespaces\n```\n\n### Certificate Secrets\n- `jaeger-demo-tls` (namespace: jaeger)\n- `opensearch-demo-tls` (namespace: opensearch)\n- `otel-demo-tls` (namespace: otel-demo)\n\n### Force Certificate Renewal\n```bash\nkubectl delete certificate <cert-name> -n <namespace>\n# Certificate will be automatically recreated\n```\n\n## Prerequisites\n\n- NGINX Ingress Controller (deployed)\n-  cert-manager (deployed)\n-  ClusterIssuer (letsencrypt-prod) configured\n-  DNS records pointing to ingress controller IP (170.9.51.232)\n\n## DNS Configuration\n\nAll hostnames must resolve to the NGINX ingress controller external IP:\n\n```\njaeger.demo.jaegertracing.io     -> 170.9.51.232\nhotrod.demo.jaegertracing.io     -> 170.9.51.232\nopensearch.demo.jaegertracing.io -> 170.9.51.232\nshop.demo.jaegertracing.io       -> 170.9.51.232\n```\n\nVerify DNS:\n```bash\ndig jaeger.demo.jaegertracing.io +short\n```\n\n\n\n## Troubleshooting\n\n### Ingress Not Working\n```bash\nkubectl get ingress --all-namespaces\nkubectl describe ingress <name> -n <namespace>\n```\n\n### Certificate Issues\n```bash\nkubectl describe certificate <cert-name> -n <namespace>\nkubectl get certificaterequest -n <namespace>\nkubectl get challenge -n <namespace>\n```\n\n### Ingress Controller Logs\n```bash\nkubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx\n```\n\n## Security Notes\n\n- Load generator is **not** directly exposed to the internet\n- Access load generator via frontend-proxy: https://shop.demo.jaegertracing.io/loadgen/\n- All certificates are production Let's Encrypt certificates\n- Auto-renewal enabled (certificates valid for 90 days) \n\n"
  },
  {
    "path": "examples/otel-demo/ingress/clusterissuer-letsencrypt-prod.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt-prod\nspec:\n  acme:\n    email: cncf-jaeger-maintainers@lists.cncf.io\n    server: https://acme-v02.api.letsencrypt.org/directory\n    privateKeySecretRef:\n      name: letsencrypt-prod\n    solvers:\n      - http01:\n          ingress:\n            ingressClassName: nginx\n"
  },
  {
    "path": "examples/otel-demo/ingress/ingress-jaeger.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: jaeger-demo-ingress\n  namespace: jaeger\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: jaeger.demo.jaegertracing.io\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: jaeger-query-clusterip\n                port:\n                  number: 16686\n    - host: hotrod.demo.jaegertracing.io\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: jaeger-hotrod\n                port:\n                  number: 80\n  tls:\n    - hosts:\n        - jaeger.demo.jaegertracing.io\n        - hotrod.demo.jaegertracing.io\n      secretName: jaeger-demo-tls\n"
  },
  {
    "path": "examples/otel-demo/ingress/ingress-opensearch.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: opensearch-demo-ingress\n  namespace: opensearch\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\nspec:\n  ingressClassName: nginx\n  rules:\n    - host: opensearch.demo.jaegertracing.io\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: opensearch-dashboards\n                port:\n                  number: 5601\n  tls:\n    - hosts:\n        - opensearch.demo.jaegertracing.io\n      secretName: opensearch-demo-tls\n"
  },
  {
    "path": "examples/otel-demo/ingress/ingress-otel-demo.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: otel-demo-ingress\n  namespace: otel-demo\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\nspec:\n  ingressClassName: nginx\n  rules:\n    - host:  shop.demo.jaegertracing.io \n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: frontend-proxy\n                port:\n                  number: 8080\n  tls:\n    - hosts:\n        - shop.demo.jaegertracing.io\n      secretName: otel-demo-tls\n"
  },
  {
    "path": "examples/otel-demo/jaeger-config.yaml",
    "content": "service:\n  extensions: [jaeger_storage, jaeger_query, healthcheckv2]\n  \n  pipelines:\n    traces:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [jaeger_storage_exporter]\n\nextensions:\n  healthcheckv2:\n    use_v2: true\n    http:\n      endpoint: 0.0.0.0:13133\n  \n  jaeger_query:\n    storage:\n      traces: some_storage\n      traces_archive: another_storage\n      metrics: some_storage  # For SPM metrics\n  \n  jaeger_storage:\n    backends:\n      some_storage: &opensearch_config\n        opensearch:\n          server_urls:\n            - http://opensearch-cluster-single.opensearch.svc.cluster.local:9200\n          indices:\n            index_prefix: \"jaeger-main\"\n            spans:\n              date_layout: \"2006-01-02\"\n              rollover_frequency: \"day\"\n              shards: 1\n              replicas: 0\n            services:\n              date_layout: \"2006-01-02\"\n              rollover_frequency: \"day\"\n              shards: 1\n              replicas: 0\n            dependencies:\n              date_layout: \"2006-01-02\"\n              rollover_frequency: \"day\"\n              shards: 1\n              replicas: 0\n            sampling:\n              date_layout: \"2006-01-02\"\n              rollover_frequency: \"day\"\n              shards: 1\n              replicas: 0\n      \n      another_storage:\n        opensearch:\n          server_urls:\n            - http://opensearch-cluster-single.opensearch.svc.cluster.local:9200\n          indices:\n            index_prefix: \"jaeger-archive\"\n            spans:\n              date_layout: \"2006-01-02\"\n              rollover_frequency: \"day\"\n              shards: 1\n              replicas: 0\n    \n    metric_backends:\n      some_storage: *opensearch_config\n\nreceivers:\n  otlp:\n    protocols:\n      grpc:\n        endpoint: \"0.0.0.0:4317\"\n      http:\n        endpoint: \"0.0.0.0:4318\"\n\nprocessors:\n  batch:\n\nexporters:\n  jaeger_storage_exporter:\n    trace_storage: some_storage\n\n"
  },
  {
    "path": "examples/otel-demo/jaeger-query-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: jaeger-query-clusterip\n  namespace: jaeger\n  labels:\n    app.kubernetes.io/name: jaeger\n    app.kubernetes.io/component: query\n    app.kubernetes.io/instance: jaeger\nspec:\n  type: ClusterIP\n  ports:\n    - name: jaeger-query\n      port: 16686\n      targetPort: 16686\n      protocol: TCP\n    - name: jaeger-admin\n      port: 16685\n      targetPort: 16685\n      protocol: TCP\n  selector:\n    app.kubernetes.io/name: jaeger\n    app.kubernetes.io/component: all-in-one\n    app.kubernetes.io/instance: jaeger\n"
  },
  {
    "path": "examples/otel-demo/jaeger-values.yaml",
    "content": "global:\n  imageRegistry: docker.io\n\nallInOne:\n  enabled: true\n  extraEnv: []\n  \n\n# Enable HotROD demo application\nhotrod:\n  enabled: true\n  image:\n    tag: \"1.72.0\"\n  args:\n    - all\n  extraArgs:\n    - --jaeger-ui=https://jaeger.demo.jaegertracing.io\n    - --otel-exporter=otlp\n  livenessProbe:\n    path: /\n  readinessProbe:\n    path: /\n  extraEnv:\n    - name: OTEL_EXPORTER_OTLP_ENDPOINT\n      value: http://jaeger-collector:4318\n    - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT\n      value: http://jaeger-collector:4318/v1/traces\n    - name: OTEL_EXPORTER_OTLP_PROTOCOL\n      value: http/protobuf\n    - name: OTEL_SERVICE_NAME\n      value: hotrod\n    - name: OTEL_LOG_LEVEL\n      value: debug\n  \n\nquery:\n  service:\n    type: ClusterIP\n"
  },
  {
    "path": "examples/otel-demo/load-generator.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: trace-generator\n  namespace: jaeger\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: trace-generator\n  template:\n    metadata:\n      labels:\n        app: trace-generator\n    spec:\n      restartPolicy: Always\n      volumes:\n      - name: script-volume\n        configMap:\n          name: trace-script\n          items:\n          - key: generate_traces.py\n            path: generate_traces.py\n      containers:\n      - name: trace-generator\n        image: python:3.11-slim\n        command:\n          - /bin/sh\n          - -c\n          - |\n            pip install --no-cache-dir requests && python /app/generate_traces.py\n        env:\n        - name: TARGET_URL\n          value: \"http://jaeger-hotrod.jaeger.svc.cluster.local/dispatch\"\n        - name: SLEEP_SECONDS\n          value: \"5\"\n        volumeMounts:\n        - name: script-volume\n          mountPath: /app\n        resources:\n          requests:\n            cpu: \"50m\"\n            memory: \"128Mi\"\n            ephemeral-storage: \"200Mi\"\n          limits:\n            cpu: \"200m\"\n            memory: \"256Mi\"\n            ephemeral-storage: \"1Gi\"\n"
  },
  {
    "path": "examples/otel-demo/opensearch-dashboard-values.yaml",
    "content": "\nimage:\n  repository: docker.io/opensearchproject/opensearch-dashboards\n  tag: \"2.11.0\"\n\nopensearchHosts: \"http://opensearch-cluster-single:9200\"\n\nsecurityContext:\n  runAsUser: 1000\n  runAsGroup: 1000\n\nconfig:\n  opensearch_dashboards.yml: |\n    server.host: 0.0.0.0\n    opensearch.hosts: [\"http://opensearch-cluster-single:9200\"]\n    opensearch.username: \"admin\"\n    opensearch.password: \"admin123\"\n    opensearch.ssl.verificationMode: none\n    opensearch_security.enabled: false\n\n"
  },
  {
    "path": "examples/otel-demo/opensearch-values.yaml",
    "content": "clusterName: \"opensearch-cluster\"\nnodeGroup: \"single\"\n\nglobal:\n  dockerRegistry: docker.io\n\nreplicas: 1\n\npersistence:\n  enabled: true\n  size: \"10Gi\"  \n  storageClass: \"oci-bv\" # Using Oracle Cloud Block Volume storage class\n\nopensearchJavaOpts: \"-Xmx1g -Xms1g\"\n\nsecurityConfig:\n  enabled: false\n\nextraEnvs:\n  - name: DISABLE_INSTALL_DEMO_CONFIG\n    value: \"true\"\n  - name: DISABLE_SECURITY_PLUGIN\n    value: \"true\"\n\n\n\nconfig:\n  opensearch.yml: |\n    cluster.name: opensearch-cluster\n    network.host: 0.0.0.0\n    bootstrap.memory_lock: false\n    plugins.security.disabled: true\n\nservice:\n  type: ClusterIP\n  port: 9200\n\n"
  },
  {
    "path": "examples/otel-demo/otel-demo-values.yaml",
    "content": "# official schema for otel demo is at https://raw.githubusercontent.com/open-telemetry/opentelemetry-helm-charts/main/charts/opentelemetry-demo/values.yaml\n\n# Keep bundled infra disabled (we deploy Jaeger/OpenSearch separately)\njaeger:\n  enabled: false\nprometheus:\n  enabled: false\ngrafana:\n  enabled: false\nopensearch:\n  enabled: false\n\n# Preserve default.env (to keep OTEL_SERVICE_NAME and OTEL_COLLECTOR_NAME) and override only what we need\ndefault:\n  envOverrides:\n    # Narrower service namespace + explicit environment tag\n    - name: OTEL_RESOURCE_ATTRIBUTES\n      value: service.name=$(OTEL_SERVICE_NAME),service.namespace=otel-demo,deployment.environment=oke-dev\n    # Send OTLP over HTTP by default and disable metrics/logs exporters (traces only)\n    - name: OTEL_EXPORTER_OTLP_ENDPOINT\n      value: http://otel-collector:4318\n    - name: OTEL_EXPORTER_OTLP_PROTOCOL\n      value: http/protobuf\n    - name: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL\n      value: http/protobuf\n    - name: OTEL_LOGS_EXPORTER\n      value: none\n    - name: OTEL_METRICS_EXPORTER\n      value: none\n    - name: OTEL_TRACES_EXPORTER\n      value: otlp\n\ncomponents:\n  accounting:\n    initContainers:\n      - name: wait-for-kafka\n        image: docker.io/busybox:latest\n        command: [\"sh\", \"-c\", \"until nc -z -v -w30 kafka 9092; do echo waiting for kafka; sleep 2; done;\"]\n\n  cart:\n    initContainers:\n      - name: wait-for-valkey-cart\n        image: docker.io/busybox:latest\n        command: [\"sh\", \"-c\", \"until nc -z -v -w30 valkey-cart 6379; do echo waiting for valkey-cart; sleep 2; done;\"]\n\n  checkout:\n    initContainers:\n      - name: wait-for-kafka\n        image: docker.io/busybox:latest\n        command: [\"sh\", \"-c\", \"until nc -z -v -w30 kafka 9092; do echo waiting for kafka; sleep 2; done;\"]\n\n  fraud-detection:\n    initContainers:\n      - name: wait-for-kafka\n        image: docker.io/busybox:latest\n        command: [\"sh\", \"-c\", \"until nc -z -v -w30 kafka 9092; do echo waiting for kafka; sleep 2; done;\"]\n\n  flagd:\n    initContainers:\n      - name: init-config\n        image: docker.io/busybox:latest\n        command: [\"sh\", \"-c\", \"cp /config-ro/demo.flagd.json /config-rw/demo.flagd.json && cat /config-rw/demo.flagd.json\"]\n        volumeMounts:\n          - mountPath: /config-ro\n            name: config-ro\n          - mountPath: /config-rw\n            name: config-rw\n\n  load-generator:\n    envOverrides:\n      - name: LOCUST_USERS\n        value: \"5\"\n      - name: LOCUST_SPAWN_RATE\n        value: \"2\"\n\n  valkey-cart:\n    imageOverride:\n      repository: docker.io/valkey/valkey\n      tag: \"8.1.3-alpine\"\n\n# Override Collector config to export traces to Jaeger only and drop demo metrics/logs\nopentelemetry-collector:\n  image:\n    repository: docker.io/otel/opentelemetry-collector-contrib\n  config:\n    receivers:\n      otlp:\n        protocols:\n          grpc: {}\n          http:\n            cors:\n              allowed_origins:\n                - http://*\n                - https://*\n      httpcheck/frontend-proxy: null\n      redis: null\n    processors:\n      memory_limiter: {}\n      k8sattributes: {}\n      resource:\n        attributes:\n          - key: service.instance.id\n            from_attribute: k8s.pod.uid\n            action: insert\n      transform: null\n      batch: {}\n    connectors:\n      spanmetrics: null\n    exporters:\n      otlp/jaeger:\n        endpoint: jaeger-collector.jaeger.svc.cluster.local:4317\n        tls:\n          insecure: true\n      opensearch: null\n      otlphttp/prometheus: null\n      debug: null\n      otlp: null\n    service:\n      telemetry: null\n      pipelines:\n        traces:\n          receivers: [otlp]\n          processors: [memory_limiter, k8sattributes, resource, batch]\n          exporters: [otlp/jaeger]\n        metrics: null\n        logs: null\n"
  },
  {
    "path": "examples/otel-demo/start-port-forward.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n# OpenSearch Observability Stack Port Forwarding Script\n\n\n# helper function to check if a service exists\ncheck_service() {\n  local service=$1\n  local namespace=$2\n  if kubectl get svc \"$service\" -n \"$namespace\" > /dev/null 2>&1; then\n    return 0\n  else\n    return 1\n  fi\n}\n\necho \"Starting Port Forwarding for OpenSearch Observability Stack\"\n\n# Check prerequisites\nif ! command -v kubectl > /dev/null 2>&1; then\n  echo \" kubectl is required but not installed\"\n  exit 1\nfi\n\nif ! kubectl cluster-info > /dev/null 2>&1; then\n  echo \"🛑 Cannot connect to Kubernetes cluster. Please ensure minikube (or the cluster) is running\"\n  exit 1\nfi\n\n# Stop any existing port forwards first\necho \" Stopping any existing port-forward processes...\"\npkill -f \"kubectl port-forward\" 2>/dev/null || true\nsleep 2\n\n# Track results\nstarted_services=()\nfailed_services=()\n\necho \"  Starting port forwarding services...\"\n\n# Jaeger Query UI\nif check_service \"jaeger-query-clusterip\" \"jaeger\"; then\n  kubectl port-forward -n jaeger svc/jaeger-query-clusterip 16686:16686 &\n  started_services+=(\"Jaeger UI (http://localhost:16686)\")\n  echo \"Started: Jaeger UI on port 16686\"\nelse\n  failed_services+=(\"Jaeger (service not found)\")\n  echo \"⚠️ Jaeger service not found\"\nfi\n\n# OpenSearch Dashboards\nif check_service \"opensearch-dashboards\" \"opensearch\"; then\n  kubectl port-forward -n opensearch svc/opensearch-dashboards 5601:5601 &\n  started_services+=(\"OpenSearch Dashboards (http://localhost:5601)\")\n  echo \"Started: OpenSearch Dashboards on port 5601\"\nelse\n  failed_services+=(\"OpenSearch Dashboards (service not found)\")\n  echo \"⚠️ OpenSearch Dashboards service not found\"\nfi\n\n# OpenSearch API\nif check_service \"opensearch-cluster-single\" \"opensearch\"; then\n  kubectl port-forward -n opensearch svc/opensearch-cluster-single 9200:9200 &\n  started_services+=(\"OpenSearch API (http://localhost:9200)\")\n  echo \"Started: OpenSearch API on port 9200\"\nelse\n  failed_services+=(\"OpenSearch API (service not found)\")\n  echo \"⚠️ OpenSearch API service not found\"\nfi\n\n# OTEL Demo Frontend\nif check_service \"frontend-proxy\" \"otel-demo\"; then\n  kubectl port-forward -n otel-demo svc/frontend-proxy 8080:8080 &\n  started_services+=(\"OTEL Demo Frontend (http://localhost:8080)\")\n  echo \"  Started: OTEL Demo Frontend on port 8080\"\nelse\n  failed_services+=(\"OTEL Demo Frontend (service not found)\")\n  echo \"⚠️ OTEL Demo Frontend service not found\"\nfi\n\n# Load Generator\nif check_service \"load-generator\" \"otel-demo\"; then\n  kubectl port-forward -n otel-demo svc/load-generator 8089:8089 &\n  started_services+=(\"Load Generator (http://localhost:8089)\")\n  echo \" Started: Load Generator on port 8089\"\nelse\n  failed_services+=(\"Load Generator (service not found)\")\n  echo \"⚠️ Load Generator service not found\"\nfi\n\n# HotROD Demo App (from Jaeger Helm chart v2)\nif check_service \"jaeger-hotrod\" \"jaeger\"; then\n  kubectl port-forward -n jaeger svc/jaeger-hotrod 8088:80 &\n  started_services+=(\"HotROD Demo App (http://localhost:8088)\")\n  echo \" Started: HotROD Demo App on port 8088\"\nelse\n  failed_services+=(\"HotROD Demo App (service not found)\")\n  echo \"⚠️ HotROD Demo App service not found\"\nfi\n\n# Wait for services to start\nsleep 3\n\necho \"\"\necho \"✅ Port Forwarding Setup Complete!\"\necho \"\"\n\nif [ ${#started_services[@]} -gt 0 ]; then\n  echo \"Successfully started services:\"\n  for service in \"${started_services[@]}\"; do\n    echo \" • $service\"\n  done\n  echo \"\"\nfi\n\nif [ ${#failed_services[@]} -gt 0 ]; then\n  echo \"Failed to start services:\"\n  for service in \"${failed_services[@]}\"; do\n    echo \" • $service\"\n  done\n  echo \"\"\n  echo \"⚠️ Some services may not be deployed yet. Run the deployment script first.\"\n  echo \"\"\nfi\n\nif [ ${#started_services[@]} -gt 0 ]; then\n  echo \"Management commands:\"\n  echo \" • View all port-forwards: jobs\"\n  echo \" • Stop all port-forwards: pkill -f 'kubectl port-forward'\"\n  echo \" • Stop this script: Ctrl+C\"\n  echo \"\"\n\n  echo \" Port forwarding is active. Press Ctrl+C to stop all port-forwards.\"\n\n  trap '\n    echo \" Stopping all port-forwards...\"\n    pkill -f \"kubectl port-forward\"\n    echo \"✅ All port-forwards stopped.\"\n    exit 0\n  ' INT\n\n  # Keep script alive\n  while true; do\n    sleep 10\n  done\nelse\n  echo \"🛑 No services were successfully started. Please check your deployment.\"\n  exit 1\nfi\n"
  },
  {
    "path": "examples/reverse-proxy/README.md",
    "content": "# reverse-proxy example\n\nThis example illustrates how Jaeger UI can be run behind a reverse proxy under a different URL prefix.\n\nStart the servers:\n\n```sh\ncd examples/reverse-proxy\ndocker compose up\n```\n\nJaeger UI can be accesssed at http://localhost:18080/jaeger/prefix .\n"
  },
  {
    "path": "examples/reverse-proxy/docker-compose.yml",
    "content": "services:\n  jaeger:\n    image: cr.jaegertracing.io/jaegertracing/jaeger:${JAEGER_VERSION:-2.5.0}\n    ports:\n      - \"16686:16686\"  # Jaeger UI\n      - \"4317:4317\"    # Collector, OpenTelemetry gRPC\n      - \"4318:4318\"    # Collector, OpenTelemetry gRPC\n    # We are using the build-in all-in-one configuration to avoid having\n    # to provide an external configuration file for Jaeger. We use the\n    # `--set` flag to override the default configuration of the `jaeger_query`\n    # extension to tell Jaeger that it should run the UI with a given prefix.\n    command: \"--set extensions.jaeger_query.base_path=/jaeger/prefix\"\n    networks:\n      - proxy-net\n\n  httpd:\n    image: httpd:latest\n    ports:\n      - \"18080:80\"\n    volumes:\n      - ./httpd.conf:/usr/local/apache2/conf/httpd.conf\n    depends_on:\n      - jaeger\n    networks:\n      - proxy-net\n\nnetworks:\n  proxy-net:\n"
  },
  {
    "path": "examples/reverse-proxy/httpd.conf",
    "content": "ServerRoot \"/usr/local/apache2\"\n\nListen 80\n\nLoadModule mpm_event_module modules/mod_mpm_event.so\nLoadModule proxy_module modules/mod_proxy.so\nLoadModule proxy_http_module modules/mod_proxy_http.so\n# some of the modules below may not be needed\nLoadModule authn_file_module modules/mod_authn_file.so\nLoadModule authn_core_module modules/mod_authn_core.so\nLoadModule authz_host_module modules/mod_authz_host.so\nLoadModule authz_groupfile_module modules/mod_authz_groupfile.so\nLoadModule authz_user_module modules/mod_authz_user.so\nLoadModule authz_core_module modules/mod_authz_core.so\nLoadModule access_compat_module modules/mod_access_compat.so\nLoadModule auth_basic_module modules/mod_auth_basic.so\nLoadModule reqtimeout_module modules/mod_reqtimeout.so\nLoadModule filter_module modules/mod_filter.so\nLoadModule mime_module modules/mod_mime.so\nLoadModule log_config_module modules/mod_log_config.so\nLoadModule env_module modules/mod_env.so\nLoadModule headers_module modules/mod_headers.so\nLoadModule setenvif_module modules/mod_setenvif.so\nLoadModule version_module modules/mod_version.so\nLoadModule unixd_module modules/mod_unixd.so\nLoadModule status_module modules/mod_status.so\nLoadModule autoindex_module modules/mod_autoindex.so\nLoadModule dir_module modules/mod_dir.so\nLoadModule alias_module modules/mod_alias.so\nLoadModule rewrite_module modules/mod_rewrite.so\n\n# Note that the prefix is used in both the proxy and the backend URLs.\nProxyPass \"/jaeger/prefix\" \"http://jaeger:16686/jaeger/prefix\"\nProxyPassReverse \"/jaeger/prefix\" \"http://jaeger:16686/jaeger/prefix\"\n\nErrorLog /proc/self/fd/2\nLogLevel info\n<IfModule log_config_module>\n    #\n    # The following directives define some format nicknames for use with\n    # a CustomLog directive (see below).\n    #\n    LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\"\" combined\n    LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b\" common\n\n    <IfModule logio_module>\n      # You need to enable mod_logio.c to use %I and %O\n      LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\" %I %O\" combinedio\n    </IfModule>\n\n    #\n    # The location and format of the access logfile (Common Logfile Format).\n    # If you do not define any access logfiles within a <VirtualHost>\n    # container, they will be logged here.  Contrariwise, if you *do*\n    # define per-<VirtualHost> access logfiles, transactions will be\n    # logged therein and *not* in this file.\n    #\n    CustomLog /proc/self/fd/1 common\n\n    #\n    # If you prefer a logfile with access, agent, and referer information\n    # (Combined Logfile Format) you can use the following directive.\n    #\n    #CustomLog \"logs/access_log\" combined\n</IfModule>\n"
  },
  {
    "path": "examples/service-performance-monitoring/README.md",
    "content": "# Service Performance Monitoring example\n\nPlease refer to [README](https://github.com/jaegertracing/jaeger/blob/main/docker-compose/monitor/README.md).\n\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/jaegertracing/jaeger\n\ngo 1.26.0\n\nrequire (\n\tgithub.com/ClickHouse/ch-go v0.71.0\n\tgithub.com/ClickHouse/clickhouse-go/v2 v2.43.0\n\tgithub.com/apache/cassandra-gocql-driver/v2 v2.0.0\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc\n\tgithub.com/dgraph-io/badger/v4 v4.9.1\n\tgithub.com/elastic/go-elasticsearch/v9 v9.3.1\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/go-logr/zapr v1.3.0\n\tgithub.com/gogo/protobuf v1.3.2\n\tgithub.com/gorilla/handlers v1.5.2\n\tgithub.com/jaegertracing/jaeger-idl v0.6.0\n\tgithub.com/kr/pretty v0.3.1\n\tgithub.com/modelcontextprotocol/go-sdk v1.4.1\n\tgithub.com/olivere/elastic/v7 v7.0.32\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.147.0\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/prometheus/client_model v0.6.2\n\tgithub.com/prometheus/common v0.67.5\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/spf13/pflag v1.0.10\n\tgithub.com/spf13/viper v1.21.0\n\tgithub.com/stretchr/testify v1.11.1\n\tgo.opentelemetry.io/collector/client v1.54.0\n\tgo.opentelemetry.io/collector/component v1.54.0\n\tgo.opentelemetry.io/collector/component/componentstatus v0.148.0\n\tgo.opentelemetry.io/collector/component/componenttest v0.148.0\n\tgo.opentelemetry.io/collector/config/configauth v1.54.0\n\tgo.opentelemetry.io/collector/config/configgrpc v0.148.0\n\tgo.opentelemetry.io/collector/config/confighttp v0.148.0\n\tgo.opentelemetry.io/collector/config/confighttp/xconfighttp v0.148.0\n\tgo.opentelemetry.io/collector/config/confignet v1.54.0\n\tgo.opentelemetry.io/collector/config/configoptional v1.54.0\n\tgo.opentelemetry.io/collector/config/configretry v1.54.0\n\tgo.opentelemetry.io/collector/config/configtls v1.54.0\n\tgo.opentelemetry.io/collector/confmap v1.54.0\n\tgo.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0\n\tgo.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0\n\tgo.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0\n\tgo.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0\n\tgo.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0\n\tgo.opentelemetry.io/collector/confmap/xconfmap v0.148.0\n\tgo.opentelemetry.io/collector/connector v0.148.0\n\tgo.opentelemetry.io/collector/connector/forwardconnector v0.148.0\n\tgo.opentelemetry.io/collector/consumer v1.54.0\n\tgo.opentelemetry.io/collector/consumer/consumertest v0.148.0\n\tgo.opentelemetry.io/collector/exporter v1.54.0\n\tgo.opentelemetry.io/collector/exporter/debugexporter v0.148.0\n\tgo.opentelemetry.io/collector/exporter/exporterhelper v0.148.0\n\tgo.opentelemetry.io/collector/exporter/exportertest v0.148.0\n\tgo.opentelemetry.io/collector/exporter/nopexporter v0.148.0\n\tgo.opentelemetry.io/collector/exporter/otlpexporter v0.148.0\n\tgo.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0\n\tgo.opentelemetry.io/collector/extension v1.54.0\n\tgo.opentelemetry.io/collector/extension/zpagesextension v0.148.0\n\tgo.opentelemetry.io/collector/featuregate v1.54.0\n\tgo.opentelemetry.io/collector/otelcol v0.148.0\n\tgo.opentelemetry.io/collector/pdata v1.54.0\n\tgo.opentelemetry.io/collector/processor v1.54.0\n\tgo.opentelemetry.io/collector/processor/batchprocessor v0.148.0\n\tgo.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0\n\tgo.opentelemetry.io/collector/processor/processorhelper v0.148.0\n\tgo.opentelemetry.io/collector/processor/processortest v0.148.0\n\tgo.opentelemetry.io/collector/receiver v1.54.0\n\tgo.opentelemetry.io/collector/receiver/nopreceiver v0.148.0\n\tgo.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0\n\tgo.opentelemetry.io/contrib/samplers/jaegerremote v0.36.0\n\tgo.opentelemetry.io/otel v1.42.0\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0\n\tgo.opentelemetry.io/otel/exporters/prometheus v0.64.0\n\tgo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0\n\tgo.opentelemetry.io/otel/metric v1.42.0\n\tgo.opentelemetry.io/otel/sdk v1.42.0\n\tgo.opentelemetry.io/otel/sdk/metric v1.42.0\n\tgo.opentelemetry.io/otel/trace v1.42.0\n\tgo.uber.org/automaxprocs v1.6.0\n\tgo.uber.org/goleak v1.3.0\n\tgo.uber.org/zap v1.27.1\n\tgolang.org/x/sys v0.42.0\n\tgoogle.golang.org/grpc v1.79.3\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tcloud.google.com/go/auth v0.17.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect\n\tgithub.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect\n\tgithub.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect\n\tgithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect\n\tgithub.com/andybalholm/brotli v1.2.0 // indirect\n\tgithub.com/apache/thrift v0.22.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect\n\tgithub.com/dennwc/varint v1.0.0 // indirect\n\tgithub.com/golang-jwt/jwt/v5 v5.3.0 // indirect\n\tgithub.com/google/jsonschema-go v0.4.2 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/gorilla/mux v1.8.1 // indirect\n\tgithub.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect\n\tgithub.com/jpillora/backoff v1.0.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.10 // indirect\n\tgithub.com/kylelemons/godebug v1.1.0 // indirect\n\tgithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/extension/internal/credentialsfile v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/internal/healthcheck v0.147.0 // indirect\n\tgithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect\n\tgithub.com/prometheus/client_golang/exp v0.0.0-20260101091701-2cd067eb23c9 // indirect\n\tgithub.com/prometheus/otlptranslator v1.0.0 // indirect\n\tgithub.com/prometheus/prometheus v0.309.2-0.20260113170727-c7bc56cf6c8f // indirect\n\tgithub.com/prometheus/sigv4 v0.3.0 // indirect\n\tgithub.com/segmentio/encoding v0.5.4 // indirect\n\tgithub.com/tg123/go-htpasswd v1.2.4 // indirect\n\tgithub.com/twmb/franz-go/pkg/kadm v1.17.2 // indirect\n\tgithub.com/xdg-go/scram v1.2.0 // indirect\n\tgithub.com/yosida95/uritemplate/v3 v3.0.2 // indirect\n\tgithub.com/zeebo/xxh3 v1.1.0 // indirect\n\tgo.opentelemetry.io/collector/config/configopaque v1.54.0 // indirect\n\tgo.opentelemetry.io/collector/internal/componentalias v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/semconv v0.128.1-0.20250610090210-188191247685 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/api v0.258.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apimachinery v0.34.3 // indirect\n\tk8s.io/client-go v0.34.3 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect\n)\n\nrequire (\n\tgithub.com/IBM/sarama v1.46.3 // indirect\n\tgithub.com/alecthomas/participle/v2 v2.1.4 // indirect\n\tgithub.com/antchfx/xmlquery v1.5.0 // indirect\n\tgithub.com/antchfx/xpath v1.3.6 // indirect\n\tgithub.com/aws/aws-msk-iam-sasl-signer-go v1.0.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.41.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/config v1.32.10 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.19.10 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect\n\tgithub.com/aws/smithy-go v1.24.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect\n\tgithub.com/dgraph-io/ristretto/v2 v2.2.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/eapache/go-resiliency v1.7.0 // indirect\n\tgithub.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect\n\tgithub.com/eapache/queue v1.1.0 // indirect\n\tgithub.com/ebitengine/purego v0.10.0 // indirect\n\tgithub.com/elastic/elastic-transport-go/v8 v8.8.0 // indirect\n\tgithub.com/elastic/go-grok v0.3.1 // indirect\n\tgithub.com/elastic/lunes v0.2.0 // indirect\n\tgithub.com/expr-lang/expr v1.17.8 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f // indirect\n\tgithub.com/go-faster/city v1.0.1 // indirect\n\tgithub.com/go-faster/errors v0.7.1 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/goccy/go-json v0.10.5 // indirect\n\tgithub.com/gogo/googleapis v1.4.1 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/snappy v1.0.0 // indirect\n\tgithub.com/google/flatbuffers v25.2.10+incompatible // indirect\n\tgithub.com/google/go-tpm v0.9.8 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hashicorp/golang-lru v1.0.2 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/iancoleman/strcase v0.3.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jcmturner/aescts/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/dnsutils/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/gofork v1.7.6 // indirect\n\tgithub.com/jcmturner/gokrb5/v8 v8.4.4 // indirect\n\tgithub.com/jcmturner/rpc/v2 v2.0.3 // indirect\n\tgithub.com/jonboulle/clockwork v0.5.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.4 // indirect\n\tgithub.com/knadh/koanf/maps v0.1.2 // indirect\n\tgithub.com/knadh/koanf/providers/confmap v1.0.0 // indirect\n\tgithub.com/knadh/koanf/v2 v2.3.3 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgithub.com/lightstep/go-expohisto v1.0.0 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect\n\tgithub.com/magefile/mage v1.15.0 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/mostynb/go-grpc-compression v1.2.3 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/internal/filter v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/internal/kafka v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/internal/pdatautil v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchpersignal v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.147.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/configkafka v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/topic v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.147.0 // indirect\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/zipkin v0.147.0 // indirect\n\tgithub.com/openzipkin/zipkin-go v0.4.3 // indirect\n\tgithub.com/paulmach/orb v0.12.0 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.26 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect\n\tgithub.com/prometheus/procfs v0.20.1 // indirect\n\tgithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect\n\tgithub.com/relvacode/iso8601 v1.7.0 // indirect\n\tgithub.com/rogpeppe/go-internal v1.14.1 // indirect\n\tgithub.com/rs/cors v1.11.1 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/sagikazarmark/locafero v0.11.0 // indirect\n\tgithub.com/segmentio/asm v1.2.1 // indirect\n\tgithub.com/shirou/gopsutil/v4 v4.26.2 // indirect\n\tgithub.com/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/spf13/cast v1.10.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.16 // indirect\n\tgithub.com/tklauser/numcpus v0.11.0 // indirect\n\tgithub.com/twmb/franz-go v1.20.7 // indirect\n\tgithub.com/twmb/franz-go/pkg/kmsg v1.12.0 // indirect\n\tgithub.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0 // indirect\n\tgithub.com/twmb/franz-go/plugin/kzap v1.1.2 // indirect\n\tgithub.com/twmb/murmur3 v1.1.8 // indirect\n\tgithub.com/ua-parser/uap-go v0.0.0-20240611065828-3a4781585db6 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/collector v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/config/configcompression v1.54.0 // indirect\n\tgo.opentelemetry.io/collector/config/configmiddleware v1.54.0\n\tgo.opentelemetry.io/collector/config/configtelemetry v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/connector/connectortest v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/connector/xconnector v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/consumer/consumererror v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/consumer/xconsumer v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/exporter/xexporter v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/extension/extensionauth v1.54.0\n\tgo.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0\n\tgo.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/extension/extensiontest v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/extension/xextension v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/internal/memorylimiter v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/internal/sharedcomponent v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/internal/telemetry v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/pdata/pprofile v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/pdata/testdata v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/pdata/xpdata v0.148.0\n\tgo.opentelemetry.io/collector/pipeline v1.54.0 // indirect\n\tgo.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/processor/xprocessor v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/receiver/receiverhelper v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/receiver/receivertest v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/receiver/xreceiver v0.148.0 // indirect\n\tgo.opentelemetry.io/collector/service v0.148.0\n\tgo.opentelemetry.io/collector/service/hostcapabilities v0.148.0 // indirect\n\tgo.opentelemetry.io/contrib/bridges/otelzap v0.17.0 // indirect\n\tgo.opentelemetry.io/contrib/otelconf v0.22.0 // indirect\n\tgo.opentelemetry.io/contrib/propagators/b3 v1.42.0 // indirect\n\tgo.opentelemetry.io/contrib/zpages v0.67.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect\n\tgo.opentelemetry.io/otel/log v0.18.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/log v0.18.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.10.0 // indirect\n\tgo.uber.org/atomic v1.11.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa\n\tgolang.org/x/text v0.34.0 // indirect\n\tgonum.org/v1/gonum v0.17.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=\ncloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0/go.mod h1:QyiQdW4f4/BIfB8ZutZ2s+28RAgfa/pT+zS++ZHyM1I=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 h1:bXwSugBiSbgtz7rOtbfGf+woewp4f06orW9OP5BjHLA=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0/go.mod h1:Y/HgrePTmGy9HjdSGTqZNa+apUpTVIEVKXJyARP2lrk=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=\ngithub.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM=\ngithub.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=\ngithub.com/ClickHouse/clickhouse-go/v2 v2.43.0 h1:fUR05TrF1GyvLDa/mAQjkx7KbgwdLRffs2n9O3WobtE=\ngithub.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g=\ngithub.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=\ngithub.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=\ngithub.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=\ngithub.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=\ngithub.com/IBM/sarama v1.46.3 h1:njRsX6jNlnR+ClJ8XmkO+CM4unbrNr/2vB5KK6UA+IE=\ngithub.com/IBM/sarama v1.46.3/go.mod h1:GTUYiF9DMOZVe3FwyGT+dtSPceGFIgA+sPc5u6CBwko=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=\ngithub.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=\ngithub.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=\ngithub.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=\ngithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=\ngithub.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=\ngithub.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=\ngithub.com/antchfx/xmlquery v1.5.0 h1:uAi+mO40ZWfyU6mlUBxRVvL6uBNZ6LMU4M3+mQIBV4c=\ngithub.com/antchfx/xmlquery v1.5.0/go.mod h1:lJfWRXzYMK1ss32zm1GQV3gMIW/HFey3xDZmkP1SuNc=\ngithub.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=\ngithub.com/antchfx/xpath v1.3.6 h1:s0y+ElRRtTQdfHP609qFu0+c6bglDv20pqOViQjjdPI=\ngithub.com/antchfx/xpath v1.3.6/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=\ngithub.com/apache/cassandra-gocql-driver/v2 v2.0.0 h1:Omnzb1Z/P90Dr2TbVNu54ICQL7TKVIIsJO231w484HU=\ngithub.com/apache/cassandra-gocql-driver/v2 v2.0.0/go.mod h1:QH/asJjB3mHvY6Dot6ZKMMpTcOrWJ8i9GhsvG1g0PK4=\ngithub.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=\ngithub.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=\ngithub.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=\ngithub.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\ngithub.com/aws/aws-msk-iam-sasl-signer-go v1.0.4 h1:2jAwFwA0Xgcx94dUId+K24yFabsKYDtAhCgyMit6OqE=\ngithub.com/aws/aws-msk-iam-sasl-signer-go v1.0.4/go.mod h1:MVYeeOhILFFemC/XlYTClvBjYZrg/EPd3ts885KrNTI=\ngithub.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=\ngithub.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=\ngithub.com/aws/aws-sdk-go-v2/service/ec2 v1.279.0 h1:o7eJKe6VYAnqERPlLAvDW5VKXV6eTKv1oxTpMoDP378=\ngithub.com/aws/aws-sdk-go-v2/service/ec2 v1.279.0/go.mod h1:Wg68QRgy2gEGGdmTPU/UbVpdv8sM14bUZmF64KFwAsY=\ngithub.com/aws/aws-sdk-go-v2/service/ecs v1.70.0 h1:IZpZatHsscdOKjwmDXC6idsCXmm3F/obutAUNjnX+OM=\ngithub.com/aws/aws-sdk-go-v2/service/ecs v1.70.0/go.mod h1:LQMlcWBoiFVD3vUVEz42ST0yTiaDujv2dRE6sXt1yPE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=\ngithub.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 h1:MQuZZ6Tq1qQabPlkVxrCMdyVl70Ogl4AERZKo+y9Wzo=\ngithub.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10/go.mod h1:U5C3JME1ibKESmpzBAqlRpTYZfVbTqrb5ICJm+sVVd8=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=\ngithub.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=\ngithub.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=\ngithub.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=\ngithub.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=\ngithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=\ngithub.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=\ngithub.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=\ngithub.com/dgraph-io/badger/v4 v4.9.1 h1:DocZXZkg5JJHJPtUErA0ibyHxOVUDVoXLSCV6t8NC8w=\ngithub.com/dgraph-io/badger/v4 v4.9.1/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0=\ngithub.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=\ngithub.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=\ngithub.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=\ngithub.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/digitalocean/godo v1.171.0 h1:QwpkwWKr3v7yxc8D4NQG973NoR9APCEWjYnLOQeXVpQ=\ngithub.com/digitalocean/godo v1.171.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=\ngithub.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=\ngithub.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=\ngithub.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=\ngithub.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=\ngithub.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=\ngithub.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=\ngithub.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=\ngithub.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=\ngithub.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=\ngithub.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=\ngithub.com/elastic/go-elasticsearch/v9 v9.3.1 h1:v5A9uFw0nLFA0luD3xAqliBXbscfuhch409HIinfhKY=\ngithub.com/elastic/go-elasticsearch/v9 v9.3.1/go.mod h1:B5u4H2jo2/v0+PrgbmIUdEyHdenFyavWtjciAFl7TA0=\ngithub.com/elastic/go-grok v0.3.1 h1:WEhUxe2KrwycMnlvMimJXvzRa7DoByJB4PVUIE1ZD/U=\ngithub.com/elastic/go-grok v0.3.1/go.mod h1:n38ls8ZgOboZRgKcjMY8eFeZFMmcL9n2lP0iHhIDk64=\ngithub.com/elastic/lunes v0.2.0 h1:WI3bsdOTuaYXVe2DS1KbqA7u7FOHN4o8qJw80ZyZoQs=\ngithub.com/elastic/lunes v0.2.0/go.mod h1:u3W/BdONWTrh0JjNZ21C907dDc+cUZttZrGa625nf2k=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=\ngithub.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=\ngithub.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=\ngithub.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM=\ngithub.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=\ngithub.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=\ngithub.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=\ngithub.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=\ngithub.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f h1:RJ+BDPLSHQO7cSjKBqjPJSbi1qfk9WcsjQDtZiw3dZw=\ngithub.com/foxboron/go-tpm-keyfiles v0.0.0-20251226215517-609e4778396f/go.mod h1:VHbbch/X4roIY22jL1s3qRbZhCiRIgUAF/PdSUcx2io=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=\ngithub.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=\ngithub.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=\ngithub.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=\ngithub.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=\ngithub.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=\ngithub.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=\ngithub.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM=\ngithub.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84=\ngithub.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM=\ngithub.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=\ngithub.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=\ngithub.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=\ngithub.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=\ngithub.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=\ngithub.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4=\ngithub.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY=\ngithub.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=\ngithub.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=\ngithub.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=\ngithub.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=\ngithub.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=\ngithub.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=\ngithub.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=\ngithub.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=\ngithub.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=\ngithub.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=\ngithub.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=\ngithub.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=\ngithub.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=\ngithub.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=\ngithub.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=\ngithub.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=\ngithub.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=\ngithub.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=\ngithub.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=\ngithub.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=\ngithub.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=\ngithub.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=\ngithub.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=\ngithub.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=\ngithub.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=\ngithub.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=\ngithub.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=\ngithub.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=\ngithub.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=\ngithub.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=\ngithub.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=\ngithub.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=\ngithub.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=\ngithub.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=\ngithub.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=\ngithub.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=\ngithub.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=\ngithub.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=\ngithub.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=\ngithub.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=\ngithub.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=\ngithub.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=\ngithub.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=\ngithub.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=\ngithub.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/gophercloud/gophercloud/v2 v2.9.0 h1:Y9OMrwKF9EDERcHFSOTpf/6XGoAI0yOxmsLmQki4LPM=\ngithub.com/gophercloud/gophercloud/v2 v2.9.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=\ngithub.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=\ngithub.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=\ngithub.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=\ngithub.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=\ngithub.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=\ngithub.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=\ngithub.com/hashicorp/cronexpr v1.1.3 h1:rl5IkxXN2m681EfivTlccqIryzYJSXRGRNa0xeG7NA4=\ngithub.com/hashicorp/cronexpr v1.1.3/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=\ngithub.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=\ngithub.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=\ngithub.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/nomad/api v0.0.0-20260106084653-e8f2200c7039 h1:77URO0yPjlPjRc00KbjoBTG2dqHXFKA7Fv3s98w16kM=\ngithub.com/hashicorp/nomad/api v0.0.0-20260106084653-e8f2200c7039/go.mod h1:sldFTIgs+FsUeKU3LwVjviAIuksxD8TzDOn02MYwslE=\ngithub.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=\ngithub.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=\ngithub.com/hetznercloud/hcloud-go/v2 v2.33.0 h1:g9hwuo60IXbupXJCYMlO4xDXgxxMPuFk31iOpLXDCV4=\ngithub.com/hetznercloud/hcloud-go/v2 v2.33.0/go.mod h1:GzYEl7slIGKc6Ttt08hjiJvGj8/PbWzcQf6IUi02dIs=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=\ngithub.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/ionos-cloud/sdk-go/v6 v6.3.6 h1:l/TtKgdQ1wUH3DDe2SfFD78AW+TJWdEbDpQhHkWd6CM=\ngithub.com/ionos-cloud/sdk-go/v6 v6.3.6/go.mod h1:nUGHP4kZHAZngCVr4v6C8nuargFrtvt7GrzH/hqn7c4=\ngithub.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2QqsP5KTMk=\ngithub.com/jaegertracing/jaeger-idl v0.6.0/go.mod h1:mpW0lZfG907/+o5w5OlnNnig7nHJGT3SfKmRqC42HGQ=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=\ngithub.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=\ngithub.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=\ngithub.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=\ngithub.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=\ngithub.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=\ngithub.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=\ngithub.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=\ngithub.com/knadh/koanf/v2 v2.3.3 h1:jLJC8XCRfLC7n4F+ZKKdBsbq1bfXTpuFhf4L7t94D94=\ngithub.com/knadh/koanf/v2 v2.3.3/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=\ngithub.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=\ngithub.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lightstep/go-expohisto v1.0.0 h1:UPtTS1rGdtehbbAF7o/dhkWLTDI73UifG8LbfQI7cA4=\ngithub.com/lightstep/go-expohisto v1.0.0/go.mod h1:xDXD0++Mu2FOaItXtdDfksfgxfV0z1TMPa+e/EUd0cs=\ngithub.com/linode/linodego v1.63.0 h1:MdjizfXNJDVJU6ggoJmMO5O9h4KGPGivNX0fzrAnstk=\ngithub.com/linode/linodego v1.63.0/go.mod h1:GoiwLVuLdBQcAebxAVKVL3mMYUgJZR/puOUSla04xBE=\ngithub.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=\ngithub.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=\ngithub.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=\ngithub.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=\ngithub.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=\ngithub.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=\ngithub.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=\ngithub.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=\ngithub.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc=\ngithub.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=\ngithub.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=\ngithub.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=\ngithub.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=\ngithub.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.147.0 h1:sAjARhiJxByhuUz0JTUPthqetNp6rxACW6KMEDd6K3c=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.147.0/go.mod h1:nAEpAXAz41JmUxHcHeXpg6tMwe9JL5RxpX+pGa9yJP0=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.147.0 h1:8DF9hSx66jk3eWjZjUH3+aokuUQPdCQkw9z+NqJ1oYY=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.147.0/go.mod h1:CXjSPfbi7uiP9HcOscN5ultxy9YZ4RrSAgarO9tMnAM=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter v0.147.0 h1:8k93l6lIa1W4QQOU0/OH9BEKciBIJWPmrXW9n/esXJs=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter v0.147.0/go.mod h1:dRxp6Gk8ngODlTz+Cayv3dxyfPPiE55bdV7hwBG6XwY=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.147.0 h1:5PjhFOIEALESZolVAfTC4Sg53RcIgGW/Ke5AYFQ9l5M=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.147.0/go.mod h1:47js3Z256jOh+XpOtCH+cQZtz9jW2wpcq6cWe5IVr6Q=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension v0.147.0 h1:RneYo1jyTJMqNT0HHYRHLb2LYf7f4NtwjETmG73NGS8=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension v0.147.0/go.mod h1:69PRbxCJeu2l+d+Gayubu53RFxUuuqcLU8XtmA+kLfE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/internal/credentialsfile v0.147.0 h1:BlP9xxHe4BAbABEIS8TuHr3qDfpyv81n7Bu/sQuVlps=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/internal/credentialsfile v0.147.0/go.mod h1:neAzGFqd93Rg4m1DDJm9rAWg0+0Jk76fHXHgzAfczgM=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.147.0 h1:UJnSG8N2l9t+Pr0fY/EboDFzZYB2HsMLdL6uj+OzvCo=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.147.0/go.mod h1:ClUcsFAUK0PXWazIpiZdJORKRnEk+Mp4vtdxrP2yELA=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension v0.147.0 h1:Y7xcLh9OGqYTgR7ylUR1Ht3Y1YArcM3uQXMcBlkrNus=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension v0.147.0/go.mod h1:8JIXqozseUSNyHgBd6EFT6vWuIg23RXWNMio0aD1DHc=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.147.0 h1:esfK3tYYNEea3jfmcOKJyR7Ezz9+KmI3bgF3+TwOOiQ=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.147.0/go.mod h1:+Y4GA0ade48Tu2jJ6BbsP8ZCyv7FqSvsHR+F+aUKJHY=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.147.0 h1:T7yBzSHaI7kKMe8skPtnqQvp4hp8sAmLZdQav2MDrW0=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.147.0/go.mod h1:ZsCmntCwaHQFhjYdn1CnT6+wadDNDbHqCSWjRwpr63U=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.147.0 h1:DVadnM6h9kbXkpCQLbr12Z+fuQPmAlY51iHWs5ut3fw=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.147.0/go.mod h1:aF8IuTH/4RSG3znojA0KFavdtGfykAqExX1YjOaIa5M=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.147.0 h1:rUPX31MpXODJqkrZWSyS8cnxf3lNgA69PxvV7HzBLCs=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.147.0/go.mod h1:CvU3E/36YEVyReqRvgJqW3IQXhN8Odhf6tC1yev2PHw=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/filter v0.147.0 h1:YPe60sJMIoKxQotdUClpgiIPcb7Knb/OM2qpoKgRb/c=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/filter v0.147.0/go.mod h1:vbExcaw9Q47djcw4ILpnaTZTp8n3QRJFI4xFXcSHmig=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/healthcheck v0.147.0 h1:NkLpgOdYnTCs6hUiqvr8U9UxbUM1WUkfjgvGG8MJTQo=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/healthcheck v0.147.0/go.mod h1:AkFZzL012QPWCLtLvXrQh1xTZVqBn8iIIt5zbgWdPf4=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/kafka v0.147.0 h1:/TPCPFE/1WR3zOfLropPqsQ8NSVOmst5q3C8pHPABe0=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/kafka v0.147.0/go.mod h1:ywtB5h2kNqFqSyVShjFJic0BC8sO5r2HQZ77atu+2VQ=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/pdatautil v0.147.0 h1:ROhVuI04U0/jRv3zcW9u5OTDlqyfAcg+/QsKwPbRR1s=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/internal/pdatautil v0.147.0/go.mod h1:k0+eJUJNXUoKjF2XFjZnWfBmAHRd+Rb24uI2A72amYc=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchpersignal v0.147.0 h1:TUbXDTIjnZpXCAA2MCXrRCVcsT6kwPflPd8DLzcWzsI=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchpersignal v0.147.0/go.mod h1:oHKqyWra+9FjEIJPR7W9D8QUEXrRwzE+uKgwqeOO2SE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.147.0 h1:e2UW/ZBN3Um+zHIRmCJl9rAOtIynPzTVAUQfpAt5Dtk=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.147.0/go.mod h1:tCfzrCKv5wNnJxZvi/2xJqFptOLMj1tz5PYiKqdAqHE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.147.0 h1:nxCNHHUItl2j0sjknI/mRbBBcQCxu0yv3baii9GNB1U=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.147.0/go.mod h1:LrW8KarPjlu+1VdP2t6kjJeOTF+y3/n2wCZAdc/NWg0=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/configkafka v0.147.0 h1:eH8MrShWyTuAE/mxtE8T0nExCg8tZzoS1vwDUEl2U0I=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/configkafka v0.147.0/go.mod h1:urjJHvpoRTtDExH/qoRRu1lJiTcYoK5HrInQfUjyZRE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/topic v0.147.0 h1:MZ2IvmAgWJRQyt1enQJpK5j3VUw5D7nXTr8vCQH33w8=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/topic v0.147.0/go.mod h1:CnkW4xd+Xa6MvVtuY5CKkQtEdb2Sazq7E4rRI/F7nJY=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.147.0 h1:wVGrNsA8QvEzXvR6vQOv7S94EGs/L8mUUsV70Qate4g=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.147.0/go.mod h1:FSh8OidZ963lNDB1SeEphUhK/HKj983+5gv9eVwh9gk=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.147.0 h1:jgmHvcC3WCrkA49VBm/Tay6O5OEaLvevlqd+OEoPI3M=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.147.0/go.mod h1:NMXNbNZ4aEhyW0Oe4BfbGLLOI8Y9FmB/unZp01HUlKU=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.147.0 h1:EYy8gmyjGLS0iYV7ksOOHrjZgiTjbWU26vziBAt4jKI=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.147.0/go.mod h1:VDqy65biIJI9iYN9rrVi2nm9KAvSfq+6Fzrm8WyL4Qw=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.147.0 h1:7dvd0FwpRWTWp66fbCsGf/JhYYMPSYcu9XF0cmPKc2Y=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry v0.147.0/go.mod h1:WJUHOQIEa2VG73eBj0Zqo3On4gVkCgOrtSRq7CssD1w=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.147.0 h1:frHEmZtGSx+aahM6RVW6LTKXdqrPetGpB1nix9QQ//Y=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.147.0/go.mod h1:d/AjbYIhXKqX9Rr92+iCvGMPQNIu/FE0I8N5A2cJBqo=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure v0.147.0 h1:tVhO/op0pgRN8o2kOUvKzZ1e37DKHCW/3MpegUkuJHE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure v0.147.0/go.mod h1:7mql5p8gvpK+nlHy+BiidRnMXe49QTNEcy1WOkd2fcM=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.147.0 h1:CgI9ucLeKNrA/vpTgdb3p2/WHW/MarIQKntc84xpFJE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.147.0/go.mod h1:+xw9tDl9F3N0y+ppHR8f1owl18A8d5UNKJzwrHIGQR8=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.147.0 h1:ohA/GdgjyijjdpELoGnInmkrg4fxsUYQTWs6nUdJWJ0=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.147.0/go.mod h1:wpArXVy+Y3ZvgIebJYjywfPRyltpSqrPHWJDfEfsUas=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/zipkin v0.147.0 h1:ybOAetZoXyUx/afmDveMR0sd5SHt66lH7sVQ848PgqU=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/zipkin v0.147.0/go.mod h1:lHN3dsMIZOY4txzU8BMcLsF33AAWc2nKGfFOW6pq+SQ=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.147.0 h1:evdMn3nTOwmR/77/3yT5cpehkFO2RMEdcU7NNI7DOKg=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.147.0/go.mod h1:SNgCq3hgH+QCdzeUFH70cQwsDKNpa/1OieJQUwCK7Ps=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.147.0 h1:P0wATwpMJthz0KYvJ7/IGQphirAtL+xoEf9l9Tjbl6w=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.147.0/go.mod h1:eybr2AGrtxlJBytZscy93gu0+7pMFMjtZVAQOu7djvk=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.147.0 h1:EispIh+opDvbpIVZ2qCTnlowz4NPFH01cAybzZZYshs=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.147.0/go.mod h1:1Gc7xmntFDFqZUfrLBgtMTfUGCEttfovDm5D9phnSlM=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.147.0 h1:vOuSxeHYCNc2Hi1Zq+nulZ/nRGLImtZvo3iP5zKzuRg=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.147.0/go.mod h1:UsDob5QlnmXNT00UNNA+e2OJPCH84oLTm6crfoIugi0=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.147.0 h1:4Fxs1MC5uedffj72Cbsp4PGYlS2QwYzzFMsnWqKrvXI=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.147.0/go.mod h1:KPxcGM9KYv+4l3fTYpVA/tAX0Tmh3e9LFVvMRXaCcwk=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.147.0 h1:F8jGKEu7VRz/tFE1onoklGtfcPcYLzoTTc1yqRZdwBA=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.147.0/go.mod h1:YHzzAESLZ+ecM23UheTEaZdP67xHyQxGzQ1u1AHcbKo=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.147.0 h1:+UKAkzgVjdYbeI/hErs0R20LOlzqCYIXcWuiHBcZLpY=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.147.0/go.mod h1:GIyJzng/QxPqTl+2zlXCtZflF9O6EXM4gSjHYtUgrK8=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.147.0 h1:99RtE1pkTjNkRlcwTdCOI9miK+q7DWPZ/3anBfRN2xE=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.147.0/go.mod h1:hBGGRz7YKS4GBUmk/SvKenPX+qVDIiRl6d6AsGWR4nY=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=\ngithub.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=\ngithub.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=\ngithub.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=\ngithub.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s=\ngithub.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=\ngithub.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=\ngithub.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=\ngithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=\ngithub.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=\ngithub.com/prometheus/alertmanager v0.30.0 h1:E4dnxSFXK8V2Bb8iqudlisTmaIrF3hRJSWnliG08tBM=\ngithub.com/prometheus/alertmanager v0.30.0/go.mod h1:93PBumcTLr/gNtNtM0m7BcCffbvYP5bKuLBWiOnISaA=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_golang/exp v0.0.0-20260101091701-2cd067eb23c9 h1:al1B/YzHmaXhacIFkrZSDSUpnPHV4ZPMfENQpvk3PZQ=\ngithub.com/prometheus/client_golang/exp v0.0.0-20260101091701-2cd067eb23c9/go.mod h1:PmAYDB13uBFBG9qE1qxZZgZWhg7Rg6SfKM5DMK7hjyI=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=\ngithub.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=\ngithub.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM=\ngithub.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=\ngithub.com/prometheus/exporter-toolkit v0.15.1 h1:XrGGr/qWl8Gd+pqJqTkNLww9eG8vR/CoRk0FubOKfLE=\ngithub.com/prometheus/exporter-toolkit v0.15.1/go.mod h1:P/NR9qFRGbCFgpklyhix9F6v6fFr/VQB/CVsrMDGKo4=\ngithub.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=\ngithub.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=\ngithub.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=\ngithub.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=\ngithub.com/prometheus/prometheus v0.309.2-0.20260113170727-c7bc56cf6c8f h1:jdB0ldVZ0vDSopY9ya4h/XPGlRYVEDsWH5x7+vIx4R8=\ngithub.com/prometheus/prometheus v0.309.2-0.20260113170727-c7bc56cf6c8f/go.mod h1:wSFyaZQ1ioryO2X47s2wvQEWypS+Mwf9IQ1ABDEo2Sk=\ngithub.com/prometheus/sigv4 v0.3.0 h1:QIG7nTbu0JTnNidGI1Uwl5AGVIChWUACxn2B/BQ1kms=\ngithub.com/prometheus/sigv4 v0.3.0/go.mod h1:fKtFYDus2M43CWKMNtGvFNHGXnAJJEGZbiYCmVp/F8I=\ngithub.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=\ngithub.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=\ngithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=\ngithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/relvacode/iso8601 v1.7.0 h1:BXy+V60stMP6cpswc+a93Mq3e65PfXCgDFfhvNNGrdo=\ngithub.com/relvacode/iso8601 v1.7.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=\ngithub.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=\ngithub.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=\ngithub.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=\ngithub.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=\ngithub.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=\ngithub.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=\ngithub.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=\ngithub.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=\ngithub.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=\ngithub.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=\ngithub.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=\ngithub.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=\ngithub.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=\ngithub.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=\ngithub.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=\ngithub.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=\ngithub.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=\ngithub.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=\ngithub.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=\ngithub.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=\ngithub.com/stackitcloud/stackit-sdk-go/core v0.20.1 h1:odiuhhRXmxvEvnVTeZSN9u98edvw2Cd3DcnkepncP3M=\ngithub.com/stackitcloud/stackit-sdk-go/core v0.20.1/go.mod h1:fqto7M82ynGhEnpZU6VkQKYWYoFG5goC076JWXTUPRQ=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/tg123/go-htpasswd v1.2.4 h1:HgH8KKCjdmo7jjXWN9k1nefPBd7Be3tFCTjc2jPraPU=\ngithub.com/tg123/go-htpasswd v1.2.4/go.mod h1:EKThQok9xHkun6NBMynNv6Jmu24A33XdZzzl4Q7H1+0=\ngithub.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=\ngithub.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=\ngithub.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=\ngithub.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=\ngithub.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=\ngithub.com/twmb/franz-go v1.7.0/go.mod h1:PMze0jNfNghhih2XHbkmTFykbMF5sJqmNJB31DOOzro=\ngithub.com/twmb/franz-go v1.20.7 h1:P4MGSXJjjAPP3NRGPCks/Lrq+j+twWMVl1qYCVgNmWY=\ngithub.com/twmb/franz-go v1.20.7/go.mod h1:0bRX9HZVaoueqFWhPZNi2ODnJL7DNa6mK0HeCrC2bNU=\ngithub.com/twmb/franz-go/pkg/kadm v1.17.2 h1:g5f1sAxnTkYC6G96pV5u715HWhxd66hWaDZUAQ8xHY8=\ngithub.com/twmb/franz-go/pkg/kadm v1.17.2/go.mod h1:ST55zUB+sUS+0y+GcKY/Tf1XxgVilaFpB9I19UubLmU=\ngithub.com/twmb/franz-go/pkg/kfake v0.0.0-20251021233722-4ca18825d8c0 h1:2ldj0Fktzd8IhnSZWyCnz/xulcW7zGvTLMOXTDqm7wA=\ngithub.com/twmb/franz-go/pkg/kfake v0.0.0-20251021233722-4ca18825d8c0/go.mod h1:UmQGDzMTYkAMr3CtNNYz1n0bD6KBI+cSnfQx70vP+c8=\ngithub.com/twmb/franz-go/pkg/kmsg v1.2.0/go.mod h1:SxG/xJKhgPu25SamAq0rrucfp7lbzCpEXOC+vH/ELrY=\ngithub.com/twmb/franz-go/pkg/kmsg v1.12.0 h1:CbatD7ers1KzDNgJqPbKOq0Bz/WLBdsTH75wgzeVaPc=\ngithub.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY=\ngithub.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0 h1:alKdbddkPw3rDh+AwmUEwh6HNYgTvDSFIe/GWYRR9RM=\ngithub.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0/go.mod h1:k8BoBjyUbFj34f0rRbn+Ky12sZFAPbmShrg0karAIMo=\ngithub.com/twmb/franz-go/plugin/kzap v1.1.2 h1:0arX5xJ0soUPX1LlDay6ZZoxuWkWk1lggQ5M/IgRXAE=\ngithub.com/twmb/franz-go/plugin/kzap v1.1.2/go.mod h1:53Cl9Uz1pbdOPDvUISIxLrZIWSa2jCuY1bTMauRMBmo=\ngithub.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=\ngithub.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=\ngithub.com/ua-parser/uap-go v0.0.0-20240611065828-3a4781585db6 h1:SIKIoA4e/5Y9ZOl0DCe3eVMLPOQzJxgZpfdHHeauNTM=\ngithub.com/ua-parser/uap-go v0.0.0-20240611065828-3a4781585db6/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E=\ngithub.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=\ngithub.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=\ngithub.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=\ngithub.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=\ngithub.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngithub.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=\ngithub.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=\ngithub.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=\ngithub.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngithub.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=\ngithub.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=\ngithub.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=\ngo.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=\ngo.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=\ngo.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/collector v0.148.0 h1:v/MudgCZ7n0LfOxtMIJjYdA8R073vjUllhhsaBtiTro=\ngo.opentelemetry.io/collector v0.148.0/go.mod h1:EoAnQknwq/wemQGw89xl6+IITdNwenPWl0ARNhgWwPA=\ngo.opentelemetry.io/collector/client v1.54.0 h1:JDpDdc67n2LGVcDzMKN7fSsmmB7333g6d38LshTuXR0=\ngo.opentelemetry.io/collector/client v1.54.0/go.mod h1:4ODFLlgYmMEA+GNy96Qsn6Gi2PwFQFNUScvv5vVTyfE=\ngo.opentelemetry.io/collector/component v1.54.0 h1:LvtX0Tzz18n44OrUFVk77N1FNsejfWJqztB28hrmDM8=\ngo.opentelemetry.io/collector/component v1.54.0/go.mod h1:yUMBYsySY/sDcXm8kOzEoZxt+JLdala6hxzSW0npOxY=\ngo.opentelemetry.io/collector/component/componentstatus v0.148.0 h1:sCGRaXNQolHFhPjrNJEwQ1WZOf96iL99tzm9GxuZsvg=\ngo.opentelemetry.io/collector/component/componentstatus v0.148.0/go.mod h1:yqg3SpGQc22W3wGICdnb+2kZVW9daBr3+LrGUCHkKfc=\ngo.opentelemetry.io/collector/component/componenttest v0.148.0 h1:tBXJWmy2X6KD8S0QU2YZa2zYBqP+IycSM4iOtwDD2pA=\ngo.opentelemetry.io/collector/component/componenttest v0.148.0/go.mod h1:1c1+6mZOmI0raoya5vA/X0F+fawEjNS6tCEs5xLATtA=\ngo.opentelemetry.io/collector/config/configauth v1.54.0 h1:qJ3JdalSJmKWa59kkJoD/nElPlxWvyGf3xZAVnp1TrI=\ngo.opentelemetry.io/collector/config/configauth v1.54.0/go.mod h1:vyp8mZJ793H82GV4eVuuoL+sG6n32SQgG/6jGGfOf+o=\ngo.opentelemetry.io/collector/config/configcompression v1.54.0 h1:YDnrdNSEXqam0OQWRAE+arMsvm/fxQb3oNhgcWhAZ5k=\ngo.opentelemetry.io/collector/config/configcompression v1.54.0/go.mod h1:SEcE2uFLHHPc/Vi8WCkW5MhOMUwaT321HBdZ3P8x8D0=\ngo.opentelemetry.io/collector/config/configgrpc v0.148.0 h1:UxGCpYGw55RVsNkhesRF4im6TJvB9ImaJenpwEgOWco=\ngo.opentelemetry.io/collector/config/configgrpc v0.148.0/go.mod h1:wdDhkiKYPLEfU/8dcHQaJWQiY0nbI/Z75q0YBEQ3f3Q=\ngo.opentelemetry.io/collector/config/confighttp v0.148.0 h1:1OYlN1pK0IlJrZTLiNxQNPD90AnrEjJ72HXd77w5Xqs=\ngo.opentelemetry.io/collector/config/confighttp v0.148.0/go.mod h1:bXmmkVH3L4E2XZwKOQuy/5EbOzhX97e0iuv9iMlFbXQ=\ngo.opentelemetry.io/collector/config/confighttp/xconfighttp v0.148.0 h1:P7W7AMzuf9G7Nf4S61DaPabDVECqK1oihmdxbGTaI2E=\ngo.opentelemetry.io/collector/config/confighttp/xconfighttp v0.148.0/go.mod h1:QxCETpKso9gowySOslD9Fv5lnkgzc9P9RVHxCLpu7CM=\ngo.opentelemetry.io/collector/config/configmiddleware v1.54.0 h1:FPMNDPumiZ7FhfzRggn5PR0AnPZQOVB7VWua11VGAUU=\ngo.opentelemetry.io/collector/config/configmiddleware v1.54.0/go.mod h1:6PYzhcC5402GuSjIs6Q14O2HjH2ZE+A60wWdQuI7ZhY=\ngo.opentelemetry.io/collector/config/confignet v1.54.0 h1:Led1uZQkFDSRIaO9GyZjvpIfuMBAADou7MvhtZkV/Pc=\ngo.opentelemetry.io/collector/config/confignet v1.54.0/go.mod h1:okpHzgIUQW9ga1P9PXzUsggmG1woR1rYsfZGDWKAC6c=\ngo.opentelemetry.io/collector/config/configopaque v1.54.0 h1:DsVlBIk3RDbRz48GxkrKFN5uNet8EaGXU39C6VsUjZQ=\ngo.opentelemetry.io/collector/config/configopaque v1.54.0/go.mod h1:beDuR48blgodzbJkUgMFu9vg0qxjU04tcBtb/rVEP/A=\ngo.opentelemetry.io/collector/config/configoptional v1.54.0 h1:W6MHMrVEbjw/5boxN+VXGZmMBi62IF/lf41vhuNGebU=\ngo.opentelemetry.io/collector/config/configoptional v1.54.0/go.mod h1:c8cFSCUN/A6U00janThFC64ZpyKV1viq/chPOoaqe3I=\ngo.opentelemetry.io/collector/config/configretry v1.54.0 h1:v0G/FxIkkcZzaM/1JrHN5sWBoUWWvb3c+UEgvo5iFs4=\ngo.opentelemetry.io/collector/config/configretry v1.54.0/go.mod h1:1BoQ5SvJT751bqP/5g0VTPLkNgMtvifAr2QqMCVOv2o=\ngo.opentelemetry.io/collector/config/configtelemetry v0.148.0 h1:TZPiz6T6AOvEHmKzU0cPF+CcRbJVR0c3DCqP8Orylx8=\ngo.opentelemetry.io/collector/config/configtelemetry v0.148.0/go.mod h1:vLUthxDJbDk0ZE9MXPvmSslNESDdGblIXWoDMov3UOE=\ngo.opentelemetry.io/collector/config/configtls v1.54.0 h1:Xt5oEs+q7Y6l7mdFYqSrqr0lwJilGcg9EBlCBd/jiBw=\ngo.opentelemetry.io/collector/config/configtls v1.54.0/go.mod h1:ikruZqHoIlR+MaqUgDKotQiuN64uAdlH6zxsTjSKjSM=\ngo.opentelemetry.io/collector/confmap v1.54.0 h1:RUoxQ4uAYHTI57GfHh61D00tTQsXm9T88ozrAiicByc=\ngo.opentelemetry.io/collector/confmap v1.54.0/go.mod h1:mQxG8bk0IWIt9gbWMvzE+cRkOuCuzbzkNGBq2YJ4wNM=\ngo.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0 h1:oFzvQvVf6w/H6niYNkp/I3FJpjD3bG7Phw8pad/mO60=\ngo.opentelemetry.io/collector/confmap/provider/envprovider v1.54.0/go.mod h1:NxRpjioeOYqRaqg83REYYUAIaraZapbhWsvIrDVhQ8k=\ngo.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0 h1:oypNOydhUDKyg2GBhchpwofKQbgnGrLmXkldrXD8T3Q=\ngo.opentelemetry.io/collector/confmap/provider/fileprovider v1.54.0/go.mod h1:x2HycFHWpfplIjjMERFOO9byCLLMCnuoxZ87TYwvPF4=\ngo.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0 h1:AScV+fx6izn08GRlxlZ0KZBCqv6/Q89oV0i3xqlg9zs=\ngo.opentelemetry.io/collector/confmap/provider/httpprovider v1.54.0/go.mod h1:laleeicMLVT96TNDXQcZbs0YiZnszi9xvXQFrbiS7iY=\ngo.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0 h1:/HUPm/8GW5IUc1J0wSJGh3Sc1mlDMHFcdNFL8u+ujic=\ngo.opentelemetry.io/collector/confmap/provider/httpsprovider v1.54.0/go.mod h1:qLgxp5Csz0D7v2LWM1lOwnfsc/7yN1Jx/egeuhrE3oA=\ngo.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0 h1:CjTo0rLNhcmcdk42OxC15yBz4JAUpUrEAXj/F3W3yP8=\ngo.opentelemetry.io/collector/confmap/provider/yamlprovider v1.54.0/go.mod h1:CxAzf/ESn11aY/wCUeGQg1QBEtW+KVwQZi5T50y+RCU=\ngo.opentelemetry.io/collector/confmap/xconfmap v0.148.0 h1:UW8MX5VlKJf67x4Et7J9kPwP9Rv4VSmJ+UUpgRcb//c=\ngo.opentelemetry.io/collector/confmap/xconfmap v0.148.0/go.mod h1:4qTMr3V0uSXXac9wVs/UD5fIqRKw5yIl58+Vjsc6RHM=\ngo.opentelemetry.io/collector/connector v0.148.0 h1:nJOvqm57ab4xRDxF0C+PQdptOF/x6NU9MAaqQJqOq7A=\ngo.opentelemetry.io/collector/connector v0.148.0/go.mod h1:Evipn8SpEed4NSynwcef3s/VihyutpAzv9aFh2KvtJA=\ngo.opentelemetry.io/collector/connector/connectortest v0.148.0 h1:LPrjLF9UbGOtZkG/PfA2Lh94Aouxf0FeqtL4TLvKXvY=\ngo.opentelemetry.io/collector/connector/connectortest v0.148.0/go.mod h1:y9S8I7FLfb8+nyqugOFiExv/ZlGi/BIcINUEdowX4eQ=\ngo.opentelemetry.io/collector/connector/forwardconnector v0.148.0 h1:16am6caOX+Sd0D0k9z8+9EH0inFVlVIrM0O5MW5IFKg=\ngo.opentelemetry.io/collector/connector/forwardconnector v0.148.0/go.mod h1:G4TRuamriPMNElN02qbh5AE9+fwMe8TUkEU/4DZPSNs=\ngo.opentelemetry.io/collector/connector/xconnector v0.148.0 h1:O6GOSkFezdCovPWIlcx0ZkymLGBlmMIoBrRzMLUV8ho=\ngo.opentelemetry.io/collector/connector/xconnector v0.148.0/go.mod h1:FMtp0iuWWmv2wY30QQyMbetNLn0MJfFgVXZDAViUwKs=\ngo.opentelemetry.io/collector/consumer v1.54.0 h1:RGGtUN+GbkV1px3T6XdUHmgJ+ldJ1hAHdesFzW/wgL0=\ngo.opentelemetry.io/collector/consumer v1.54.0/go.mod h1:1PC6XINTL9DdT1bwvfMdHE72EB4RWU/WcPemUrhqKN8=\ngo.opentelemetry.io/collector/consumer/consumererror v0.148.0 h1:lKVkNWBeRXG41lHBf5KzA9oErRZifx6qTd9erAFfEkE=\ngo.opentelemetry.io/collector/consumer/consumererror v0.148.0/go.mod h1:N/UppmtknIdzpEiy3xirH1EiBEBOqKqD77NCyNi2Rbc=\ngo.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0 h1:61RfzjtvnATQEahTN/Enwz0QFEBK9M9eNcxHh5Etzm0=\ngo.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.148.0/go.mod h1:vJSXpbjZelXtXdV3AjdGC2WjoVQkNLpzxy+5MUl3Xd8=\ngo.opentelemetry.io/collector/consumer/consumertest v0.148.0 h1:ms0HtWMj17tI1Yds0hSuUI5QYpNEqd11AAhwIoUY2HE=\ngo.opentelemetry.io/collector/consumer/consumertest v0.148.0/go.mod h1:wScw/OzKkf/ZzJn4ToI30OoI1kJiY16WNrcFToXSzK0=\ngo.opentelemetry.io/collector/consumer/xconsumer v0.148.0 h1:m3b9rY7CLD5Pcge6sSKHIT3OlcPN6xqYsdtVs9oJ528=\ngo.opentelemetry.io/collector/consumer/xconsumer v0.148.0/go.mod h1:bG+Wz6xmIBl/gHzq1sqvksWXqTLuTX17Wo//zIsdZpw=\ngo.opentelemetry.io/collector/exporter v1.54.0 h1:SSkEc9VGCf4OJaf+spj4euZ/FcswzOwLm8zR9an5Fxc=\ngo.opentelemetry.io/collector/exporter v1.54.0/go.mod h1:thsNaoV7xRq91sXkKsyFXHj0l2c/ZDM88Mdwe2/QP40=\ngo.opentelemetry.io/collector/exporter/debugexporter v0.148.0 h1:wIKpbnB9YJCYHGwL6gm7Yb45QW31H/ii2RZxoJBmD1E=\ngo.opentelemetry.io/collector/exporter/debugexporter v0.148.0/go.mod h1:szSrW/yxBwNJUleotXfkuESh/uII62YOS7JxDS2iwU0=\ngo.opentelemetry.io/collector/exporter/exporterhelper v0.148.0 h1:mZXGdleKMaEF0jSOcCoOVRWwt3AcgSTAnIZmAqdDYNs=\ngo.opentelemetry.io/collector/exporter/exporterhelper v0.148.0/go.mod h1:+EZCJ6vlgQiozHvUoeEJHnIaV6Ez7HHOLdNWNpo+CUc=\ngo.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0 h1:LpTc/OsKXy/MBIdpCJ0VC9BCJreH5JUE8DIaNRlw488=\ngo.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.148.0/go.mod h1:bc03Yf1kCAG4LwkztlOR9NXKBZ6dMfnJLV6SL77zAzQ=\ngo.opentelemetry.io/collector/exporter/exportertest v0.148.0 h1:joLVWwfWDk7idnikGPeOWOa7nJG1pG1+jGvuuOOB1/E=\ngo.opentelemetry.io/collector/exporter/exportertest v0.148.0/go.mod h1:R202E9bjYU4R+2jiDt+aiZSwsIZI3slL6M8y1MeuqkM=\ngo.opentelemetry.io/collector/exporter/nopexporter v0.148.0 h1:d6uRdmAIM+I4SZkRao7aXy3esy4Ki19AYoi1hPXvrnY=\ngo.opentelemetry.io/collector/exporter/nopexporter v0.148.0/go.mod h1:MJC9zsIZcKmJZGWIvqyxyq2MaGDWt5vir278opk8BrY=\ngo.opentelemetry.io/collector/exporter/otlpexporter v0.148.0 h1:oBcYpuuRdK0gZNXO/zCQViSZTn/JH/Z9j/tuqpmd+q4=\ngo.opentelemetry.io/collector/exporter/otlpexporter v0.148.0/go.mod h1:Eavhwd/38JzeDeiDRsO8WFY2+FPmw0pSJt8k1tMz2d0=\ngo.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0 h1:hFUKoEOzOS0yJpLX9ucLjl0paXqFRArhbplHTVEb7y8=\ngo.opentelemetry.io/collector/exporter/otlphttpexporter v0.148.0/go.mod h1:YY2DVennR8C1uPeldeuPNLj8rUcabF2Pb+SDOpKvFwA=\ngo.opentelemetry.io/collector/exporter/xexporter v0.148.0 h1:QKMwwrUe4snzB9B97NaBtf9qFEeIjx4/oBSwv8EZbJc=\ngo.opentelemetry.io/collector/exporter/xexporter v0.148.0/go.mod h1:rZ60Z9Ny4H+IX5dsn+RiJEJQRNEXAEYZ6XFwE2EWxGU=\ngo.opentelemetry.io/collector/extension v1.54.0 h1:nF+pPfXWcWXjauX0+E1gsWUlUdAe2+26VKIb9hKZJAk=\ngo.opentelemetry.io/collector/extension v1.54.0/go.mod h1:hqjEnkrjjxLXjzyDnLsOJnWMLWkfEjbqm8CHj1ud5pY=\ngo.opentelemetry.io/collector/extension/extensionauth v1.54.0 h1:IglgKxygOcGCCCB31bBxOYwtB8h1oQ2MXVGWKV0k1C0=\ngo.opentelemetry.io/collector/extension/extensionauth v1.54.0/go.mod h1:5SXF5D0r+uhrHU50xCXAnJ1HNmSDDuXamD+fZdcYRLs=\ngo.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.148.0 h1:k2Hk5VhnWkn5C79tkZ554KAydyf0awfaW6Ku/bttS6s=\ngo.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.148.0/go.mod h1:LhiPIqE7pIDo0+Njo9gPtrAbpnx4tjzqVCP8C0UFBvQ=\ngo.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0 h1:nhIKJyE5YDy0KkI1mrULLBxMwLsq/EyeXQJJZDSRXHI=\ngo.opentelemetry.io/collector/extension/extensioncapabilities v0.148.0/go.mod h1:WQEEnK/GdM4n5EwEUL5PYimT4JFYgGzUrT7yAe8yaxI=\ngo.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0 h1:GdlmwwQ1IxExKL27Ou5YRCs91Z8QYzlENUOBax252bc=\ngo.opentelemetry.io/collector/extension/extensionmiddleware v0.148.0/go.mod h1:ySiHSkCzMcgphWdZiGYIPrFgaEGO2tPY3D0MipGsYpo=\ngo.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.148.0 h1:SgNl5DswPxs+gDH5Ojg8xyorogbxTqXoayLGZkvdB/A=\ngo.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.148.0/go.mod h1:WMKYe+WIhgCnXAmOtn69yY7tTZZqgSkx+lh1Y5tk3OI=\ngo.opentelemetry.io/collector/extension/extensiontest v0.148.0 h1:ZBrYWe+7oQQVqChXrq4PL1P/Febuo6un2/g7oQP9zfQ=\ngo.opentelemetry.io/collector/extension/extensiontest v0.148.0/go.mod h1:wLxKb/SkoqbStm6zv+9MAhzhySI49oGw2aszPaw9No4=\ngo.opentelemetry.io/collector/extension/xextension v0.148.0 h1:LoSXaI3jd7fhQbPdIDpXy0HC2j4ftsG7LlVrUrghtwA=\ngo.opentelemetry.io/collector/extension/xextension v0.148.0/go.mod h1:dlMQsSTo8Jgd+u8/ssdg0oIItQptkUIfX4zO9xy+hiE=\ngo.opentelemetry.io/collector/extension/zpagesextension v0.148.0 h1:s2M8HLykiRB7Ub5qyTsYoeJ5hR9MdsG9FJR9wricyVM=\ngo.opentelemetry.io/collector/extension/zpagesextension v0.148.0/go.mod h1:+x2vb3TFNtE32qCv6ScG//RpAYdAZYE6ok4Ua++DWkU=\ngo.opentelemetry.io/collector/featuregate v1.54.0 h1:ufo5Hy4Co9pcHVg24hyanm8qFG3TkkYbVyQXPVAbwDc=\ngo.opentelemetry.io/collector/featuregate v1.54.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g=\ngo.opentelemetry.io/collector/internal/componentalias v0.148.0 h1:Y6MftNIZSzOr47TTj6A2z2UR3IwbeG46sAQshicGtDg=\ngo.opentelemetry.io/collector/internal/componentalias v0.148.0/go.mod h1:uwKzfehzwRgHxdHgFXYSBHNBeWSSqsqQYGWr5fk08G0=\ngo.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0 h1:Vy5HOsm6IODqbg7ZHaGizcs0mXXU7yZYFTH9Be0u4mM=\ngo.opentelemetry.io/collector/internal/fanoutconsumer v0.148.0/go.mod h1:0wG5wD4+XPIrrS69j1DnUvCbfAvnhMqcrxPvQkWzdpo=\ngo.opentelemetry.io/collector/internal/memorylimiter v0.148.0 h1:5BPGr7lLc0jRneMIF4YUCeuz47ZrOkWFZ8JT0MWc15s=\ngo.opentelemetry.io/collector/internal/memorylimiter v0.148.0/go.mod h1:jWgo0uP5lLTyLVwoso+958eCodbUAJQ57GHXXL52P2E=\ngo.opentelemetry.io/collector/internal/sharedcomponent v0.148.0 h1:6SB7YuKaBvUzQOiZzT7MxbiMm5KzwNDiml/T4Thzogs=\ngo.opentelemetry.io/collector/internal/sharedcomponent v0.148.0/go.mod h1:ofyvdfavSSSD/AN49eoIxg6HskpOGfYXBQKpLfVxisI=\ngo.opentelemetry.io/collector/internal/telemetry v0.148.0 h1:7U/be+11agYLb67lzoRzsCBoDpaGy8vDFhgI1gGYcco=\ngo.opentelemetry.io/collector/internal/telemetry v0.148.0/go.mod h1:pvflQkIAaj5UwURlkaB8BNTaYw6OjmXTbiWQ75PnYqc=\ngo.opentelemetry.io/collector/internal/testutil v0.148.0 h1:3Z9hperte3vSmbBTYeNndoEUICICrNz8hzx+v0FYXBQ=\ngo.opentelemetry.io/collector/internal/testutil v0.148.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE=\ngo.opentelemetry.io/collector/otelcol v0.148.0 h1:MFhR9u5SMJG3WcT+ON0aV8CV7lIuBWo0o7DQM1TXWtE=\ngo.opentelemetry.io/collector/otelcol v0.148.0/go.mod h1:ocDXLyaKKJOPyb7A5Mr0VuIJWgqUtiL6qpispV/xVv8=\ngo.opentelemetry.io/collector/pdata v1.54.0 h1:3LharKb792cQ3VrUGxd3IcpWwfu3ST+GSTU382jVz1s=\ngo.opentelemetry.io/collector/pdata v1.54.0/go.mod h1:+MqC3VVOv/EX9YVFUo+mI4F0YmwJ+fXBYwjmu+mRiZ8=\ngo.opentelemetry.io/collector/pdata/pprofile v0.148.0 h1:MgrNZmqwhZGfiYwcKKtM/iXgTZqqvG5dUphriRXMZHU=\ngo.opentelemetry.io/collector/pdata/pprofile v0.148.0/go.mod h1:MTTMnZPqWX1S/rBDatU0W19udlycBkWuzVV5qnemHdc=\ngo.opentelemetry.io/collector/pdata/testdata v0.148.0 h1:yzakPuFgoKK8WcrlhyYHLMLA/kLScQKGsXkIgwieAQ8=\ngo.opentelemetry.io/collector/pdata/testdata v0.148.0/go.mod h1:2rFvxm8qwd3nlO90FtJw6ZGAjt+bLndxmQuJaMO9kfQ=\ngo.opentelemetry.io/collector/pdata/xpdata v0.148.0 h1:pTXz872QDl5oHByjlIEkQhIFvv0oeX/5cKNWsUg9KeY=\ngo.opentelemetry.io/collector/pdata/xpdata v0.148.0/go.mod h1:4iL8wugmu589aQNx0dFVT3Ecui/d3TEvVgMlAu8S//0=\ngo.opentelemetry.io/collector/pipeline v1.54.0 h1:jYlCkdFLITVBdeB+IGS07zXWywEgvT3Ky46vdKKT+Ks=\ngo.opentelemetry.io/collector/pipeline v1.54.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs=\ngo.opentelemetry.io/collector/pipeline/xpipeline v0.148.0 h1:WTgUC/QXYxhWEwPQ0ezOMbkh4p4DzsRdCxdYLBqNz+U=\ngo.opentelemetry.io/collector/pipeline/xpipeline v0.148.0/go.mod h1:ECXG1qs+H1pUnK0Wu0MUlAbsUlzJOKhV9z4wqep6KWQ=\ngo.opentelemetry.io/collector/processor v1.54.0 h1:zmHBFiEFmU9ZYuHhVP3lHIkbfy+ueapzGpTdXVMcWBg=\ngo.opentelemetry.io/collector/processor v1.54.0/go.mod h1:L0lA6DZ0VbrtQBg44cmYfSpRlgm4zxW1I6QfBnRizPw=\ngo.opentelemetry.io/collector/processor/batchprocessor v0.148.0 h1:RN/NU7giTuTCeWsbFmtk27rBprzJv4xfj4KDYzROEyc=\ngo.opentelemetry.io/collector/processor/batchprocessor v0.148.0/go.mod h1:i79zRqG29xhLEX0rpOLo7dqdEAIiVZPcV4l3eRsLCJM=\ngo.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0 h1:sfYYAKBp5kwnCSwIyAxK+3ShENfBkOgTGDG+ioMF2AQ=\ngo.opentelemetry.io/collector/processor/memorylimiterprocessor v0.148.0/go.mod h1:x8q27G9adfJ3rJTjNyoIHyuCpuJZI6pMbSIipJLb0sE=\ngo.opentelemetry.io/collector/processor/processorhelper v0.148.0 h1:qbX7EJ7QJAP47PIdo9Jzxj39VP5Nke48uwP52HYs/O0=\ngo.opentelemetry.io/collector/processor/processorhelper v0.148.0/go.mod h1:ZuNKoLZ3jMZQ+hsA6XFz3GmH6l23eLsYSgHB0qdAf/U=\ngo.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper v0.148.0 h1:8KTlVM8PGGuLwOTMhfeAaH2ok+aEccdbPe5Z2JPf/xw=\ngo.opentelemetry.io/collector/processor/processorhelper/xprocessorhelper v0.148.0/go.mod h1:xojdMQqttMu9kh6xi/oG81gnslB3rSHSMZkKfhmiB8c=\ngo.opentelemetry.io/collector/processor/processortest v0.148.0 h1:p0k59frZxy/Z4fXe82i5eOJv/UyOH75XhI8nFD1ZWCE=\ngo.opentelemetry.io/collector/processor/processortest v0.148.0/go.mod h1:E2Li2gnkUXgvApvGyEtn3Eq5KyzV05ljfbFRsZ7sTC4=\ngo.opentelemetry.io/collector/processor/xprocessor v0.148.0 h1:v7Qv6k2b2cvgGWuTO5KN5QYDLl1r5sznt7Le4Fhpa4c=\ngo.opentelemetry.io/collector/processor/xprocessor v0.148.0/go.mod h1:r7ADpSX2nf0rZR9STxh956Qw1740QOWMXLnEM/ZiaF8=\ngo.opentelemetry.io/collector/receiver v1.54.0 h1:2e9o+eihZ/nJnzVj5JAcJ+VQ653HcZRiT127qBZRqa8=\ngo.opentelemetry.io/collector/receiver v1.54.0/go.mod h1:xFZnvYTBjdi9iS/d/UUXzss4h311mLsZliQFQXk4o/k=\ngo.opentelemetry.io/collector/receiver/nopreceiver v0.148.0 h1:vV0RbFwSwW0hzM/6Y4fNnGXePmbc8D3TiLo/eV5irTA=\ngo.opentelemetry.io/collector/receiver/nopreceiver v0.148.0/go.mod h1:Pb7uEA+VjQwy6nVsC7zd/Bnf40UGyO2za/+I2ikwDs4=\ngo.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0 h1:npsN3tAw4941EJAdSD9DRPSvyc9uCr9r07rOO3WVd6E=\ngo.opentelemetry.io/collector/receiver/otlpreceiver v0.148.0/go.mod h1:5Yumcgp457+ki1/1vnWt+U+tJnauJkO411xKseL/6jw=\ngo.opentelemetry.io/collector/receiver/receiverhelper v0.148.0 h1:B1JOFfdv1dj4WhxSSt3KL1+BOV7Zkf27KisTWdhiFLs=\ngo.opentelemetry.io/collector/receiver/receiverhelper v0.148.0/go.mod h1:jBJbrMZ1dUn/gKr9vEDmU+MPsrz9RhRFWooG72qhUkU=\ngo.opentelemetry.io/collector/receiver/receivertest v0.148.0 h1:Fu+B4jCqgZVZmhsKBz3tcgimFryR6TRAK2D5VGLD2Xc=\ngo.opentelemetry.io/collector/receiver/receivertest v0.148.0/go.mod h1:K8dMDMEggEg6jB688VOHutivOGEEZ20FJGe4jV9RtWU=\ngo.opentelemetry.io/collector/receiver/xreceiver v0.148.0 h1:u66Zi3udD9RMRiNOsZzsVcUjRwqJEK+5LV76Ry9l3K0=\ngo.opentelemetry.io/collector/receiver/xreceiver v0.148.0/go.mod h1:jyHxf8SOfH48ZXb32IS3vPbVYDinsLlZYQddyrveqMg=\ngo.opentelemetry.io/collector/semconv v0.128.1-0.20250610090210-188191247685 h1:XCN7qkZRNzRYfn6chsMZkbFZxoFcW6fZIsZs2aCzcbc=\ngo.opentelemetry.io/collector/semconv v0.128.1-0.20250610090210-188191247685/go.mod h1:OPXer4l43X23cnjLXIZnRj/qQOjSuq4TgBLI76P9hns=\ngo.opentelemetry.io/collector/service v0.148.0 h1:GsAx4nkGTB21QRK9hOTFmLcATN/mugLWsb3iQwt91nY=\ngo.opentelemetry.io/collector/service v0.148.0/go.mod h1:f7oBS9IdX0nLRtyPOIgPj0Q1HCqbxepgWJfEpyVNAfE=\ngo.opentelemetry.io/collector/service/hostcapabilities v0.148.0 h1:BHQV7Fa1y8fQ87V1ieXNpP4+7UGOAj66xWryXSSj27I=\ngo.opentelemetry.io/collector/service/hostcapabilities v0.148.0/go.mod h1:UwHkux+xSVl7k5PEl+qYi8VSONv538rgZeHhfYqBwmE=\ngo.opentelemetry.io/collector/service/telemetry/telemetrytest v0.148.0 h1:0KKY0VHy8y+6LRkW/jE7a2G96tK7rfpl/6hKCt3mHD4=\ngo.opentelemetry.io/collector/service/telemetry/telemetrytest v0.148.0/go.mod h1:uhEy3Ez2aJjGpAIYuy1C0NFO5yr86EJlJGoKFucXQFE=\ngo.opentelemetry.io/contrib/bridges/otelzap v0.17.0 h1:oCltVHJcblcth2z9B9dRTeZIZTe2Sf9Ad9h8bcc+s8M=\ngo.opentelemetry.io/contrib/bridges/otelzap v0.17.0/go.mod h1:G/VE1A/hRn6mEWdfC8rMvSdQVGM64KUPi4XilLkwcQw=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=\ngo.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 h1:OXSUzgmIFkcC4An+mv+lqqZSndTffXpjAyoR+1f8k/A=\ngo.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0/go.mod h1:1A4GVLFIm54HFqVdOpWmukap7rgb0frrE3zWXohLPdM=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=\ngo.opentelemetry.io/contrib/otelconf v0.22.0 h1:+kpcfczGOFM85zDZyqQCzWefhovegfn24D0WwmQz0n4=\ngo.opentelemetry.io/contrib/otelconf v0.22.0/go.mod h1:ojdbOukO+JRDJQmJY2PRIZEg0UYVzcOuZR59hp7xffc=\ngo.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=\ngo.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=\ngo.opentelemetry.io/contrib/samplers/jaegerremote v0.36.0 h1:h8kHGv9+VIiJbQ2Qx6BbORZwcvVnd0le/SFK8Vom0bA=\ngo.opentelemetry.io/contrib/samplers/jaegerremote v0.36.0/go.mod h1:tjrgaYHDx+1CmTk5YzNAUCbLX1ZrjrsogXBQHaVf7rI=\ngo.opentelemetry.io/contrib/zpages v0.67.0 h1:cIUwWSVDovuLEbDIKreptjdxMuIhGiqwq0uL8YNaq1c=\ngo.opentelemetry.io/contrib/zpages v0.67.0/go.mod h1:vK8fsYHgPYg4Z/XDbFSEvItSGZDbjWTvjBOu8+AiDhc=\ngo.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=\ngo.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=\ngo.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=\ngo.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=\ngo.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=\ngo.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=\ngo.opentelemetry.io/otel/log/logtest v0.18.0 h1:2QeyoKJdIgK2LJhG1yn78o/zmpXx1EditeyRDREqVS8=\ngo.opentelemetry.io/otel/log/logtest v0.18.0/go.mod h1:v1vh3PYR9zIa5MK6HwkH2lMrLBg/Y9Of6Qc+krlesX0=\ngo.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=\ngo.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=\ngo.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=\ngo.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=\ngo.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=\ngo.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=\ngo.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=\ngo.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=\ngo.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=\ngo.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=\ngo.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=\ngo.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=\ngo.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=\ngo.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=\ngo.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM=\ngo.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk=\ngo.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ=\ngo.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U=\ngo.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0=\ngo.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4=\ngo.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=\ngo.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=\ngo.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=\ngo.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=\ngolang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=\ngolang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=\ngolang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=\ngolang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=\ngonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=\ngoogle.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc=\ngoogle.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=\ngopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=\nk8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=\nk8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=\nk8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=\nk8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=\nk8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=\nk8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=\nk8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=\nk8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=\nsigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "internal/auth/apikey/apikey-context.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage apikey\n\nimport \"context\"\n\n// apiKeyContextKey is the type used as a key for storing API keys in context.\ntype apiKeyContextKey struct{}\n\n// GetAPIKey retrieves the API key from the context.\nfunc GetAPIKey(ctx context.Context) (string, bool) {\n\tval := ctx.Value(apiKeyContextKey{})\n\tif val == nil {\n\t\treturn \"\", false\n\t}\n\tif apiKey, ok := val.(string); ok {\n\t\treturn apiKey, true\n\t}\n\treturn \"\", false\n}\n\n// ContextWithAPIKey sets the API key in the context if the key is non-empty.\nfunc ContextWithAPIKey(ctx context.Context, apiKey string) context.Context {\n\tif apiKey == \"\" {\n\t\treturn ctx\n\t}\n\treturn context.WithValue(ctx, apiKeyContextKey{}, apiKey)\n}\n"
  },
  {
    "path": "internal/auth/apikey/apikey-context_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage apikey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetAPIKey(t *testing.T) {\n\t// No value in context\n\temptyCtx := context.Background()\n\tapiKey, ok := GetAPIKey(emptyCtx)\n\tassert.Empty(t, apiKey)\n\tassert.False(t, ok)\n\n\t// Correct string value in context (via ContextWithAPIKey)\n\texpectedApiKey := \"test-api-key\"\n\tctxWithApiKey := ContextWithAPIKey(context.Background(), expectedApiKey)\n\tapiKey, ok = GetAPIKey(ctxWithApiKey)\n\tassert.Equal(t, expectedApiKey, apiKey)\n\tassert.True(t, ok)\n\n\t// Non-string value in context (simulate misuse)\n\tctxWithNonString := context.WithValue(context.Background(), apiKeyContextKey{}, 123)\n\tapiKey, ok = GetAPIKey(ctxWithNonString)\n\tassert.Empty(t, apiKey)\n\tassert.False(t, ok)\n\n\t// No API key when empty string passed to ContextWithAPIKey\n\temptyStringCtx := ContextWithAPIKey(context.Background(), \"\")\n\tapiKey, ok = GetAPIKey(emptyStringCtx)\n\tassert.Empty(t, apiKey)\n\tassert.False(t, ok)\n}\n\nfunc TestContextWithAPIKey(t *testing.T) {\n\tbaseCtx := context.Background()\n\n\t// Non-empty apiKey: should set value in context\n\tapiKey := \"my-secret-key\"\n\tctxWithKey := ContextWithAPIKey(baseCtx, apiKey)\n\tval, ok := GetAPIKey(ctxWithKey)\n\tassert.True(t, ok, \"apiKey should be present in context\")\n\tassert.Equal(t, apiKey, val)\n\n\t// Empty apiKey: should return original context, no value set\n\temptyCtx := ContextWithAPIKey(baseCtx, \"\")\n\tval, ok = GetAPIKey(emptyCtx)\n\tassert.False(t, ok, \"apiKey should not be present for empty string\")\n\tassert.Empty(t, val)\n\n\t// Should not mutate original context\n\tval, ok = GetAPIKey(baseCtx)\n\tassert.False(t, ok, \"original context should remain unchanged\")\n\tassert.Empty(t, val)\n}\n"
  },
  {
    "path": "internal/auth/apikey/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage apikey\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/context.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport \"context\"\n\ntype contextKeyType int\n\nconst contextKey = contextKeyType(iota)\n\n// StoragePropagationKey is a key for viper configuration to pass this option to storage plugins.\nconst StoragePropagationKey = \"storage.propagate.token\"\n\n// ContextWithBearerToken set bearer token in context.\nfunc ContextWithBearerToken(ctx context.Context, token string) context.Context {\n\tif token == \"\" {\n\t\treturn ctx\n\t}\n\treturn context.WithValue(ctx, contextKey, token)\n}\n\n// GetBearerToken from context, or empty string if there is no token.\nfunc GetBearerToken(ctx context.Context) (string, bool) {\n\tval, ok := ctx.Value(contextKey).(string)\n\treturn val, ok\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/context_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_GetBearerToken(t *testing.T) {\n\tconst token = \"blah\"\n\tctx := context.Background()\n\tctx = ContextWithBearerToken(ctx, token)\n\tcontextToken, ok := GetBearerToken(ctx)\n\tassert.True(t, ok)\n\tassert.Equal(t, token, contextToken)\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/grpc.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nconst Key = \"bearer.token\"\n\ntype tokenatedServerStream struct {\n\tgrpc.ServerStream\n\tcontext context.Context\n}\n\nfunc (tss *tokenatedServerStream) Context() context.Context {\n\treturn tss.context\n}\n\n// extract bearer token from the metadata\nfunc ValidTokenFromGRPCMetadata(ctx context.Context, bearerHeader string) (string, error) {\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn \"\", nil\n\t}\n\n\ttokens := md.Get(bearerHeader)\n\tif len(tokens) < 1 {\n\t\treturn \"\", nil\n\t}\n\tif len(tokens) > 1 {\n\t\treturn \"\", errors.New(\"malformed token: multiple tokens found\")\n\t}\n\treturn tokens[0], nil\n}\n\n// NewStreamServerInterceptor creates a new stream interceptor that injects the bearer token into the context if available.\nfunc NewStreamServerInterceptor() grpc.StreamServerInterceptor {\n\treturn func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\t\tif token, _ := GetBearerToken(ss.Context()); token != \"\" {\n\t\t\treturn handler(srv, ss)\n\t\t}\n\n\t\tbearerToken, err := ValidTokenFromGRPCMetadata(ss.Context(), Key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn handler(srv, &tokenatedServerStream{\n\t\t\tServerStream: ss,\n\t\t\tcontext:      ContextWithBearerToken(ss.Context(), bearerToken),\n\t\t})\n\t}\n}\n\n// NewUnaryServerInterceptor creates a new unary interceptor that injects the bearer token into the context if available.\nfunc NewUnaryServerInterceptor() grpc.UnaryServerInterceptor {\n\treturn func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {\n\t\tif token, _ := GetBearerToken(ctx); token != \"\" {\n\t\t\treturn handler(ctx, req)\n\t\t}\n\n\t\tbearerToken, err := ValidTokenFromGRPCMetadata(ctx, Key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn handler(ContextWithBearerToken(ctx, bearerToken), req)\n\t}\n}\n\n// NewUnaryClientInterceptor injects the bearer token header into gRPC request metadata.\nfunc NewUnaryClientInterceptor() grpc.UnaryClientInterceptor {\n\treturn grpc.UnaryClientInterceptor(func(\n\t\tctx context.Context,\n\t\tmethod string,\n\t\treq, reply any,\n\t\tcc *grpc.ClientConn,\n\t\tinvoker grpc.UnaryInvoker,\n\t\topts ...grpc.CallOption,\n\t) error {\n\t\tvar token string\n\t\ttoken, err := ValidTokenFromGRPCMetadata(ctx, Key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif token == \"\" {\n\t\t\tbearerToken, ok := GetBearerToken(ctx)\n\t\t\tif ok && bearerToken != \"\" {\n\t\t\t\ttoken = bearerToken\n\t\t\t}\n\t\t}\n\n\t\tif token != \"\" {\n\t\t\tctx = metadata.AppendToOutgoingContext(ctx, Key, token)\n\t\t}\n\t\treturn invoker(ctx, method, req, reply, cc, opts...)\n\t})\n}\n\n// NewStreamClientInterceptor injects the bearer token header into gRPC request metadata.\nfunc NewStreamClientInterceptor() grpc.StreamClientInterceptor {\n\treturn grpc.StreamClientInterceptor(func(\n\t\tctx context.Context,\n\t\tdesc *grpc.StreamDesc,\n\t\tcc *grpc.ClientConn,\n\t\tmethod string,\n\t\tstreamer grpc.Streamer,\n\t\topts ...grpc.CallOption,\n\t) (grpc.ClientStream, error) {\n\t\tvar token string\n\t\ttoken, err := ValidTokenFromGRPCMetadata(ctx, Key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif token == \"\" {\n\t\t\tbearerToken, ok := GetBearerToken(ctx)\n\t\t\tif ok && bearerToken != \"\" {\n\t\t\t\ttoken = bearerToken\n\t\t\t}\n\t\t}\n\n\t\tif token != \"\" {\n\t\t\tctx = metadata.AppendToOutgoingContext(ctx, Key, token)\n\t\t}\n\t\treturn streamer(ctx, desc, cc, method, opts...)\n\t})\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/grpc_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\ntype mockServerStream struct {\n\tctx context.Context\n\tgrpc.ServerStream\n}\n\nfunc (s *mockServerStream) Context() context.Context {\n\treturn s.ctx\n}\n\nfunc TestClientInterceptors(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tctx         context.Context\n\t\texpectedErr string\n\t\texpectedMD  metadata.MD\n\t}{\n\t\t{\n\t\t\tname:        \"no token in context\",\n\t\t\tctx:         context.Background(),\n\t\t\texpectedErr: \"\",\n\t\t\texpectedMD:  nil, // Expecting no metadata\n\t\t},\n\t\t{\n\t\t\tname:        \"token in context\",\n\t\t\tctx:         ContextWithBearerToken(context.Background(), \"test-token\"),\n\t\t\texpectedErr: \"\",\n\t\t\texpectedMD:  metadata.MD{Key: []string{\"test-token\"}},\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple tokens in metadata\",\n\t\t\tctx:         metadata.NewIncomingContext(context.Background(), metadata.MD{Key: []string{\"token1\", \"token2\"}}),\n\t\t\texpectedErr: \"malformed token: multiple tokens found\",\n\t\t},\n\t\t{\n\t\t\tname:        \"valid token in metadata\",\n\t\t\tctx:         metadata.NewIncomingContext(context.Background(), metadata.MD{Key: []string{\"valid-token\"}}),\n\t\t\texpectedErr: \"\",\n\t\t\texpectedMD:  metadata.MD{Key: []string{\"valid-token\"}}, // Valid token setup\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// Unary interceptor test\n\t\t\tverifyMetadata := func(ctx context.Context) error {\n\t\t\t\tmd, ok := metadata.FromOutgoingContext(ctx)\n\t\t\t\tif test.expectedMD == nil {\n\t\t\t\t\trequire.False(t, ok, \"metadata should not be present\")\n\t\t\t\t} else {\n\t\t\t\t\trequire.True(t, ok, \"metadata should be present\")\n\t\t\t\t\tassert.Equal(t, test.expectedMD, md)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tunaryInterceptor := NewUnaryClientInterceptor()\n\t\t\tunaryInvoker := func(ctx context.Context, _ string, _, _ any, _ *grpc.ClientConn, _ ...grpc.CallOption) error {\n\t\t\t\treturn verifyMetadata(ctx)\n\t\t\t}\n\t\t\terr := unaryInterceptor(test.ctx, \"method\", nil, nil, nil, unaryInvoker)\n\t\t\tif test.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t\t}\n\n\t\t\t// Stream interceptor test\n\t\t\tstreamInterceptor := NewStreamClientInterceptor()\n\t\t\tstreamInvoker := func(ctx context.Context, _ *grpc.StreamDesc, _ *grpc.ClientConn, _ string, _ ...grpc.CallOption) (grpc.ClientStream, error) {\n\t\t\t\tif err := verifyMetadata(ctx); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\t_, err = streamInterceptor(test.ctx, &grpc.StreamDesc{}, nil, \"method\", streamInvoker)\n\t\t\tif test.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.ErrorContains(t, err, test.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestServerInterceptors(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tctx         context.Context\n\t\texpectedErr string\n\t\twantToken   string\n\t}{\n\t\t{\n\t\t\tname:        \"no token in context\",\n\t\t\tctx:         context.Background(),\n\t\t\texpectedErr: \"\",\n\t\t\twantToken:   \"\",\n\t\t},\n\t\t{\n\t\t\tname:        \"token in context\",\n\t\t\tctx:         ContextWithBearerToken(context.Background(), \"test-token\"),\n\t\t\texpectedErr: \"\",\n\t\t\twantToken:   \"test-token\",\n\t\t},\n\t\t{\n\t\t\tname: \"multiple tokens in metadata\",\n\t\t\tctx: metadata.NewIncomingContext(context.Background(), metadata.MD{\n\t\t\t\tKey: []string{\"token1\", \"token2\"},\n\t\t\t}),\n\t\t\texpectedErr: \"malformed token: multiple tokens found\",\n\t\t\twantToken:   \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid token in metadata\",\n\t\t\tctx: metadata.NewIncomingContext(context.Background(), metadata.MD{\n\t\t\t\tKey: []string{\"valid-token\"},\n\t\t\t}),\n\t\t\texpectedErr: \"\",\n\t\t\twantToken:   \"valid-token\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tverifyToken := func(ctx context.Context) error {\n\t\t\t\ttoken, ok := GetBearerToken(ctx)\n\t\t\t\tif test.wantToken == \"\" {\n\t\t\t\t\tassert.False(t, ok, \"expected no token\")\n\t\t\t\t} else {\n\t\t\t\t\tassert.True(t, ok, \"expected token to be present\")\n\t\t\t\t\tassert.Equal(t, test.wantToken, token)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// Test unary server interceptor\n\t\t\tunaryInterceptor := NewUnaryServerInterceptor()\n\t\t\tunaryHandler := func(ctx context.Context, _ any) (any, error) {\n\t\t\t\treturn nil, verifyToken(ctx)\n\t\t\t}\n\n\t\t\t_, err := unaryInterceptor(test.ctx, nil, &grpc.UnaryServerInfo{}, unaryHandler)\n\t\t\tif test.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t\t}\n\n\t\t\t// Test stream server interceptor\n\t\t\tstreamInterceptor := NewStreamServerInterceptor()\n\t\t\tmockStream := &mockServerStream{ctx: test.ctx}\n\t\t\tstreamHandler := func(_ any, stream grpc.ServerStream) error {\n\t\t\t\treturn verifyToken(stream.Context())\n\t\t\t}\n\n\t\t\terr = streamInterceptor(nil, mockStream, &grpc.StreamServerInfo{}, streamHandler)\n\t\t\tif test.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.ErrorContains(t, err, test.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTokenatedServerStream(t *testing.T) {\n\toriginalCtx := context.Background()\n\ttestToken := \"test-token\"\n\tnewCtx := ContextWithBearerToken(originalCtx, testToken)\n\n\tstream := &tokenatedServerStream{\n\t\tServerStream: &mockServerStream{ctx: originalCtx},\n\t\tcontext:      newCtx,\n\t}\n\n\t// Verify that Context() returns the modified context\n\ttoken, ok := GetBearerToken(stream.Context())\n\trequire.True(t, ok)\n\tassert.Equal(t, testToken, token)\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/http.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\n\t\"go.uber.org/zap\"\n)\n\n// PropagationHandler returns a http.Handler containing the logic to extract\n// the Bearer token from the Authorization header of the http.Request and insert it into request.Context\n// for propagation. The token can be accessed via GetBearerToken.\nfunc PropagationHandler(logger *zap.Logger, h http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tctx := r.Context()\n\t\tauthHeaderValue := r.Header.Get(\"Authorization\")\n\t\t// If no Authorization header is present, try with X-Forwarded-Access-Token\n\t\tif authHeaderValue == \"\" {\n\t\t\tauthHeaderValue = r.Header.Get(\"X-Forwarded-Access-Token\")\n\t\t}\n\t\tif authHeaderValue != \"\" {\n\t\t\theaderValue := strings.Split(authHeaderValue, \" \")\n\t\t\ttoken := \"\"\n\t\t\tswitch {\n\t\t\tcase len(headerValue) == 2:\n\t\t\t\t// Make sure we only capture bearer token , not other types like Basic auth.\n\t\t\t\tif headerValue[0] == \"Bearer\" {\n\t\t\t\t\ttoken = headerValue[1]\n\t\t\t\t}\n\t\t\tcase len(headerValue) == 1:\n\t\t\t\t// Treat the entire value as a token.\n\t\t\t\ttoken = authHeaderValue\n\t\t\tdefault:\n\t\t\t\tlogger.Warn(\"Invalid authorization header value, skipping token propagation\")\n\t\t\t}\n\t\t\th.ServeHTTP(w, r.WithContext(ContextWithBearerToken(ctx, token)))\n\t\t} else {\n\t\t\th.ServeHTTP(w, r.WithContext(ctx))\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/http_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n)\n\nfunc Test_PropagationHandler(t *testing.T) {\n\thttpClient := &http.Client{\n\t\tTimeout: 2 * time.Second,\n\t}\n\n\tlogger := zap.NewNop()\n\tconst bearerToken = \"blah\"\n\n\tvalidTokenHandler := func(stop *sync.WaitGroup) http.HandlerFunc {\n\t\treturn func(_ http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ttoken, ok := GetBearerToken(ctx)\n\t\t\tassert.Equal(t, bearerToken, token)\n\t\t\tassert.True(t, ok)\n\t\t\tstop.Done()\n\t\t}\n\t}\n\n\temptyHandler := func(stop *sync.WaitGroup) http.HandlerFunc {\n\t\treturn func(_ http.ResponseWriter, r *http.Request) {\n\t\t\tctx := r.Context()\n\t\t\ttoken, _ := GetBearerToken(ctx)\n\t\t\tassert.Empty(t, token, bearerToken)\n\t\t\tstop.Done()\n\t\t}\n\t}\n\n\ttestCases := []struct {\n\t\tname        string\n\t\tsendHeader  bool\n\t\theaderValue string\n\t\theaderName  string\n\t\thandler     func(stop *sync.WaitGroup) http.HandlerFunc\n\t}{\n\t\t{name: \"Bearer token\", sendHeader: true, headerName: \"Authorization\", headerValue: \"Bearer \" + bearerToken, handler: validTokenHandler},\n\t\t{name: \"Raw bearer token\", sendHeader: true, headerName: \"Authorization\", headerValue: bearerToken, handler: validTokenHandler},\n\t\t{name: \"No headerValue\", sendHeader: false, headerName: \"Authorization\", handler: emptyHandler},\n\t\t{name: \"Basic Auth\", sendHeader: true, headerName: \"Authorization\", headerValue: \"Basic \" + bearerToken, handler: emptyHandler},\n\t\t{name: \"X-Forwarded-Access-Token\", headerName: \"X-Forwarded-Access-Token\", sendHeader: true, headerValue: \"Bearer \" + bearerToken, handler: validTokenHandler},\n\t\t{name: \"Invalid header\", headerName: \"X-Forwarded-Access-Token\", sendHeader: true, headerValue: \"Bearer \" + bearerToken + \" another stuff\", handler: emptyHandler},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tstop := sync.WaitGroup{}\n\t\t\tstop.Add(1)\n\t\t\tr := PropagationHandler(logger, testCase.handler(&stop))\n\t\t\tserver := httptest.NewServer(r)\n\t\t\tdefer server.Close()\n\t\t\treq, err := http.NewRequest(http.MethodGet, server.URL, http.NoBody)\n\t\t\trequire.NoError(t, err)\n\t\t\tif testCase.sendHeader {\n\t\t\t\treq.Header.Add(testCase.headerName, testCase.headerValue)\n\t\t\t}\n\t\t\t_, err = httpClient.Do(req)\n\t\t\trequire.NoError(t, err)\n\t\t\tstop.Wait()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/auth/bearertoken/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage bearertoken\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/auth/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage auth\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/auth/tokenloader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage auth\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n)\n\n// CachedFileTokenLoader returns a function that loads a token from the given file path,\n// caches it for the specified interval, and reloads after the interval expires.\n// disable Reloading by setting interval to 0\nfunc cachedFileTokenLoader(path string, interval time.Duration, timeFn func() time.Time) func() (string, error) {\n\tvar (\n\t\tmu          sync.Mutex\n\t\tcachedToken string\n\t\tlastRead    time.Time\n\t)\n\n\treturn func() (string, error) {\n\t\tmu.Lock()\n\t\tdefer mu.Unlock()\n\n\t\tnow := timeFn()\n\n\t\t// Special case: interval = 0 means \"never reload after first load\"\n\t\t// Otherwise reload only if `interval` time has passed since last load.\n\t\tif !lastRead.IsZero() && (interval == 0 || now.Sub(lastRead) < interval) {\n\t\t\treturn cachedToken, nil\n\t\t}\n\n\t\t// Read from file\n\t\tb, err := os.ReadFile(filepath.Clean(path))\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to read token file: %w\", err)\n\t\t}\n\n\t\tcachedToken = strings.TrimRight(string(b), \"\\r\\n\")\n\t\tlastRead = now\n\t\treturn cachedToken, nil\n\t}\n}\n\n// TokenProvider creates a token provider that handles file loading and error handling consistently.\nfunc TokenProvider(path string, interval time.Duration, logger *zap.Logger) (func() string, error) {\n\treturn TokenProviderWithTime(path, interval, logger, time.Now) // Use real time.Now in production\n}\n\n// TokenProviderWithTime creates a token provider with injectable time (for testing)\nfunc TokenProviderWithTime(path string, interval time.Duration, logger *zap.Logger, timeFn func() time.Time) (func() string, error) {\n\tloader := cachedFileTokenLoader(path, interval, timeFn)\n\n\t// current token load\n\tcurrentToken, err := loader()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get token from file: %w\", err)\n\t}\n\n\treturn func() string {\n\t\tnewToken, err := loader()\n\t\tif err != nil {\n\t\t\tlogger.Warn(\"Token reload failed\", zap.Error(err))\n\t\t\treturn currentToken\n\t\t}\n\n\t\t// save it in case the load fails later (e.g. if file is removed)\n\t\tcurrentToken = newToken\n\t\treturn currentToken\n\t}, nil\n}\n"
  },
  {
    "path": "internal/auth/tokenloader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage auth\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"go.uber.org/zap/zaptest/observer\"\n)\n\n// TestCachedFileTokenLoader_Deterministic covers basic cache and reload logic with mock time\nfunc TestCachedFileTokenLoader_Deterministic(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\n\ttokenFile := createTempTokenFile(t, \"my-secret-token\\n\")\n\n\tloader := cachedFileTokenLoader(tokenFile, 100*time.Millisecond, timeFn)\n\n\t// T=0: First load - should read from file\n\ttoken1, err := loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"my-secret-token\", token1)\n\n\t// Change the file content\n\tupdateTokenFile(t, tokenFile, \"new-token\\n\")\n\n\t// T=50ms: Still within cache interval (< 100ms)\n\tcurrentTime = currentTime.Add(50 * time.Millisecond)\n\ttoken2, err := loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"my-secret-token\", token2, \"Should return cached token within interval\")\n\n\t// T=150ms: Beyond cache interval (> 100ms)\n\tcurrentTime = currentTime.Add(100 * time.Millisecond) // Total: 150ms\n\ttoken3, err := loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"new-token\", token3, \"Should return refreshed token after cache expires\")\n}\n\n// TestCachedFileTokenLoader_ExactBoundaries tests exact cache boundary conditions\nfunc TestCachedFileTokenLoader_ExactBoundaries(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\ttokenFile := createTempTokenFile(t, \"boundary-token\\n\")\n\tcacheInterval := 200 * time.Millisecond\n\n\tloader := cachedFileTokenLoader(tokenFile, cacheInterval, timeFn)\n\n\t// Load initial token\n\ttoken, err := loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"boundary-token\", token)\n\n\t// Update file\n\tupdateTokenFile(t, tokenFile, \"boundary-updated\\n\")\n\n\t// Test exact boundary conditions\n\ttestCases := []struct {\n\t\ttimeAdvance   time.Duration\n\t\texpectedToken string\n\t\tdescription   string\n\t}{\n\t\t{199 * time.Millisecond, \"boundary-token\", \"1ms before cache expires\"},\n\t\t{1 * time.Millisecond, \"boundary-updated\", \"exactly at cache expiry\"},\n\t\t{50 * time.Millisecond, \"boundary-updated\", \"well past cache expiry\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\tcurrentTime = currentTime.Add(tc.timeAdvance)\n\t\t\ttoken, err := loader()\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tc.expectedToken, token, tc.description)\n\t\t})\n\t}\n}\n\n// TestCachedFileTokenLoader_ZeroInterval tests disabled reloading\nfunc TestCachedFileTokenLoader_ZeroInterval(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\ttokenFile := createTempTokenFile(t, \"initial-token\\n\")\n\tloader := cachedFileTokenLoader(tokenFile, 0, timeFn) // Zero interval = no reloading\n\n\t// T=0: Initial load\n\ttoken, err := loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"initial-token\", token)\n\n\t// Update file content\n\tupdateTokenFile(t, tokenFile, \"updated-token\\n\")\n\n\t// Should still return cached token (no reloading)\n\tcurrentTime = currentTime.Add(1 * time.Hour) // Advance time significantly\n\ttoken, err = loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"initial-token\", token, \"Zero interval should disable reloading completely\")\n\n\t// Multiple calls should continue returning cached token\n\tfor i := range 5 {\n\t\t// Generate different content for each iteration\n\t\tnewContent := fmt.Sprintf(\"different-token-%d-%d\\n\", i, currentTime.Unix())\n\t\tupdateTokenFile(t, tokenFile, newContent)\n\n\t\tcurrentTime = currentTime.Add(1 * time.Hour)\n\t\ttoken, err = loader()\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"initial-token\", token,\n\t\t\t\"Should always return initially cached token despite file change to %s at time %v\",\n\t\t\tstrings.TrimSpace(newContent), currentTime)\n\t}\n}\n\n// TestNewTokenProvider_InitialLoad covers initial load and fail-fast scenarios\nfunc TestNewTokenProvider_InitialLoad(t *testing.T) {\n\t// Test successful initial load\n\ttokenFile := createTempTokenFile(t, \"initial-token\\n\")\n\n\ttokenFn, err := TokenProvider(tokenFile, 100*time.Millisecond, nil)\n\trequire.NoError(t, err, \"TokenProvider should not fail with valid token file\")\n\tassert.Equal(t, \"initial-token\", tokenFn(), \"Token should match file contents\")\n\n\t// Test fail-fast on invalid file\n\t_, err = TokenProvider(\"/nonexistent/file\", 100*time.Millisecond, nil)\n\trequire.Error(t, err, \"TokenProvider should fail fast on missing file\")\n\tassert.Contains(t, err.Error(), \"failed to get token from file\", \"Error message should indicate token loading failure\")\n\n\t// Test empty file\n\temptyFile := createTempTokenFile(t, \"\")\n\ttokenFn, err = TokenProvider(emptyFile, 100*time.Millisecond, nil)\n\trequire.NoError(t, err)\n\tassert.Empty(t, tokenFn(), \"Empty file should return empty token\")\n\t// Test file with trailing whitespace - properly trimmed\n\twhitespaceFile := createTempTokenFile(t, \"my-secret-token\\r\\n\")\n\ttokenFn, err = TokenProvider(whitespaceFile, 100*time.Millisecond, nil)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"my-secret-token\", tokenFn(), \"\\\\r\\\\n should be properly trimmed from end\")\n}\n\n// TestNewTokenProvider_ReloadErrors_Deterministic ensures reload errors log and return cached token\nfunc TestNewTokenProvider_ReloadErrors_Deterministic(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\ttokenFile := createTempTokenFile(t, \"initial-token\\n\")\n\n\t// Create an observed zap logger\n\tcore, logs := observer.New(zapcore.InfoLevel)\n\tlogger := zap.New(core)\n\n\t// Initialize token provider with mock time\n\ttokenFn, err := TokenProviderWithTime(tokenFile, 10*time.Millisecond, logger, timeFn)\n\trequire.NoError(t, err)\n\n\t// Initial call should succeed\n\ttoken := tokenFn()\n\tassert.Equal(t, \"initial-token\", token)\n\n\t// Remove the file to force reload error\n\tos.Remove(tokenFile)\n\n\t// Advance time beyond cache interval\n\tcurrentTime = currentTime.Add(15 * time.Millisecond)\n\n\t// Call should return last cached token and log error\n\ttoken = tokenFn()\n\tassert.Equal(t, \"initial-token\", token, \"Should return cached token even after file deletion\")\n\n\t// Verify the error was logged\n\trequire.Equal(t, 1, logs.Len(), \"Expected one log message\")\n\tlogEntry := logs.All()[0]\n\tassert.Equal(t, \"Token reload failed\", logEntry.Message, \"Expected log message to match\")\n\tassert.Equal(t, zapcore.WarnLevel, logEntry.Level, \"Expected warning level log\")\n}\n\n// TestTokenProviderWithTime_DirectCall tests the time-injectable function directly\nfunc TestTokenProviderWithTime_DirectCall(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\ttokenFile := createTempTokenFile(t, \"time-test-token\\n\")\n\tlogger := zap.NewNop()\n\n\t// Create token provider with mock time\n\ttokenFn, err := TokenProviderWithTime(tokenFile, 200*time.Millisecond, logger, timeFn)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, tokenFn)\n\n\t// Test initial token\n\ttoken := tokenFn()\n\tassert.Equal(t, \"time-test-token\", token)\n\n\t// Update file\n\tupdateTokenFile(t, tokenFile, \"time-updated\\n\")\n\n\t// Should still return cached token\n\tcurrentTime = currentTime.Add(100 * time.Millisecond)\n\ttoken = tokenFn()\n\tassert.Equal(t, \"time-test-token\", token)\n\n\t// Should return updated token after cache expires\n\tcurrentTime = currentTime.Add(150 * time.Millisecond)\n\ttoken = tokenFn()\n\tassert.Equal(t, \"time-updated\", token)\n}\n\n// TestNewTokenProvider_WithZapLogger ensures zap logger is used properly\nfunc TestNewTokenProvider_WithZapLogger(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\ttokenFile := createTempTokenFile(t, \"initial-token\\n\")\n\n\t// Create an observed zap logger\n\tcore, logs := observer.New(zapcore.InfoLevel)\n\tlogger := zap.New(core)\n\n\t// Initialize token provider with structured logger\n\ttokenFn, err := TokenProviderWithTime(tokenFile, 10*time.Millisecond, logger, timeFn)\n\trequire.NoError(t, err)\n\n\t// Initial call should succeed\n\ttoken := tokenFn()\n\tassert.Equal(t, \"initial-token\", token)\n\n\t// No logs yet\n\tassert.Equal(t, 0, logs.Len(), \"No logs should be emitted for successful loads\")\n\n\t// Remove the file to force reload error\n\tos.Remove(tokenFile)\n\n\t// Advance time to trigger reload\n\tcurrentTime = currentTime.Add(15 * time.Millisecond)\n\n\t// Call should log using zap logger\n\ttokenFn()\n\tassert.Equal(t, 1, logs.Len(), \"Error should be logged using zap\")\n\n\tlogEntry := logs.All()[0]\n\tassert.Equal(t, \"Token reload failed\", logEntry.Message, \"Log message should match\")\n\tassert.NotNil(t, logEntry.Context[0].Interface, \"Error should be attached to log\")\n}\n\n// TestCachedFileTokenLoader_FilePermissions tests file permission errors\nfunc TestCachedFileTokenLoader_FilePermissions(t *testing.T) {\n\tif os.Getuid() == 0 {\n\t\tt.Skip(\"Running as root - file permission tests not meaningful\")\n\t}\n\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\n\t// Create a file and make it unreadable\n\ttokenFile := createTempTokenFile(t, \"permission-test\\n\")\n\terr := os.Chmod(tokenFile, 0o000) // No permissions\n\trequire.NoError(t, err)\n\n\t// Cleanup with restored permissions\n\tt.Cleanup(func() {\n\t\tos.Chmod(tokenFile, 0o644)\n\t\tos.Remove(tokenFile)\n\t})\n\n\tloader := cachedFileTokenLoader(tokenFile, 100*time.Millisecond, timeFn)\n\n\t// Should return permission error\n\t_, err = loader()\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"permission denied\")\n}\n\nfunc TestTokenProvider_Wrapper(t *testing.T) {\n\ttokenFile := createTempTokenFile(t, \"production-test-token\\n\")\n\tlogger := zap.NewNop()\n\n\t// Test that TokenProvider uses real time\n\ttokenFn, err := TokenProvider(tokenFile, 100*time.Millisecond, logger)\n\trequire.NoError(t, err)\n\n\ttoken := tokenFn()\n\tassert.Equal(t, \"production-test-token\", token)\n}\n\n// Helper functions\n\n// createTempTokenFile creates a temp file with the given content and returns its name\nfunc createTempTokenFile(t *testing.T, content string) string {\n\tt.Helper()\n\n\ttmpFile, err := os.CreateTemp(t.TempDir(), \"token-*.txt\")\n\trequire.NoError(t, err, \"Failed to create temp file\")\n\n\t_, err = tmpFile.WriteString(content)\n\trequire.NoError(t, err, \"Failed to write to temp file\")\n\n\terr = tmpFile.Close()\n\trequire.NoError(t, err, \"Failed to close temp file\")\n\n\t// Cleanup happens automatically with t.TempDir()\n\treturn tmpFile.Name()\n}\n\n// updateTokenFile updates an existing token file with new content\nfunc updateTokenFile(t *testing.T, filename, content string) {\n\tt.Helper()\n\n\terr := os.WriteFile(filename, []byte(content), 0o600)\n\trequire.NoError(t, err, \"Failed to update token file\")\n}\n"
  },
  {
    "path": "internal/auth/transport.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Method represents a single authentication method configuration\ntype Method struct {\n\t// Scheme is the authentication scheme (e.g., \"Bearer\")\n\tScheme string\n\n\t// TokenFn returns the authentication token\n\tTokenFn func() string\n\n\t// FromCtx extracts token from context\n\tFromCtx func(context.Context) (string, bool)\n}\n\n// RoundTripper wraps another http.RoundTripper and injects\n// an authentication header with  token into requests.\ntype RoundTripper struct {\n\t// Transport is the underlying http.RoundTripper being wrapped. Required.\n\tTransport http.RoundTripper\n\tAuths     []Method\n}\n\n// RoundTrip injects the outbound Authorization header with the\n// token provided in the inbound request.\nfunc (tr RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {\n\tif tr.Transport == nil {\n\t\treturn nil, errors.New(\"no http.RoundTripper provided\")\n\t}\n\n\treq := r.Clone(r.Context())\n\n\tfor _, auth := range tr.Auths {\n\t\ttoken := \"\"\n\n\t\t// Get token from context if available\n\t\tif auth.FromCtx != nil {\n\t\t\tif t, ok := auth.FromCtx(r.Context()); ok {\n\t\t\t\ttoken = t\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to TokenFn if no token from context\n\t\tif token == \"\" && auth.TokenFn != nil {\n\t\t\ttoken = auth.TokenFn()\n\t\t}\n\n\t\t// Add Authorization header if we have a token\n\t\tif token != \"\" {\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"%s %s\", auth.Scheme, token))\n\t\t}\n\t}\n\n\treturn tr.Transport.RoundTrip(req)\n}\n"
  },
  {
    "path": "internal/auth/transport_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage auth\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n)\n\ntype roundTripFunc func(r *http.Request) (*http.Response, error)\n\nfunc (s roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {\n\treturn s(r)\n}\n\nfunc TestRoundTripper(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\tauths              []Method\n\t\trequestContext     context.Context\n\t\texpectedHeaders    []string // Expected Authorization headers\n\t\texpectError        bool\n\t\texpectNoAuthHeader bool\n\t}{\n\t\t{\n\t\t\tname:               \"No auth configs - no headers added\",\n\t\t\tauths:              []Method{},\n\t\t\trequestContext:     context.Background(),\n\t\t\texpectNoAuthHeader: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Single Bearer auth with static token\",\n\t\t\tauths: []Method{\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"Bearer\",\n\t\t\t\t\tTokenFn: func() string { return \"static-token\" },\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestContext:  context.Background(),\n\t\t\texpectedHeaders: []string{\"Bearer static-token\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Bearer auth with context override - context token used\",\n\t\t\tauths: []Method{\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"Bearer\",\n\t\t\t\t\tTokenFn: func() string { return \"static-token\" },\n\t\t\t\t\tFromCtx: bearertoken.GetBearerToken,\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestContext:  bearertoken.ContextWithBearerToken(context.Background(), \"context-token\"),\n\t\t\texpectedHeaders: []string{\"Bearer context-token\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple auth methods - both tokens added\",\n\t\t\tauths: []Method{\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"Bearer\",\n\t\t\t\t\tTokenFn: func() string { return \"bearer-token\" },\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"ApiKey\",\n\t\t\t\t\tTokenFn: func() string { return \"api-key-value\" },\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestContext:  context.Background(),\n\t\t\texpectedHeaders: []string{\"Bearer bearer-token\", \"ApiKey api-key-value\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Auth config with empty token - no header added\",\n\t\t\tauths: []Method{\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"Bearer\",\n\t\t\t\t\tTokenFn: func() string { return \"\" }, // Empty token\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"ApiKey\",\n\t\t\t\t\tTokenFn: func() string { return \"valid-key\" },\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestContext:  context.Background(),\n\t\t\texpectedHeaders: []string{\"ApiKey valid-key\"}, // Only valid token\n\t\t},\n\t\t{\n\t\t\tname: \"Only context extraction, no context token - no header added\",\n\t\t\tauths: []Method{\n\t\t\t\t{\n\t\t\t\t\tScheme:  \"Bearer\",\n\t\t\t\t\tFromCtx: bearertoken.GetBearerToken,\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestContext:     context.Background(),\n\t\t\texpectNoAuthHeader: true,\n\t\t},\n\t\t{\n\t\t\tname:           \"Nil transport - should return error\",\n\t\t\tauths:          []Method{},\n\t\t\trequestContext: context.Background(),\n\t\t\texpectError:    true,\n\t\t},\n\t\t{\n\t\t\tname: \"Basic auth with pre-encoded credentials\",\n\t\t\tauths: []Method{\n\t\t\t\t{\n\t\t\t\t\tScheme: \"Basic\",\n\t\t\t\t\tTokenFn: func() string {\n\t\t\t\t\t\t// Pre-encoded \"user:pass\"\n\t\t\t\t\t\treturn base64.StdEncoding.EncodeToString([]byte(\"user:pass\"))\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestContext:  context.Background(),\n\t\t\texpectedHeaders: []string{\"Basic dXNlcjpwYXNz\"}, // base64(\"user:pass\")\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel() // Enable parallel execution for faster tests\n\n\t\t\t// Each test gets its own isolated capturedRequest and transport\n\t\t\tvar capturedRequest *http.Request\n\t\t\twrappedTransport := roundTripFunc(func(r *http.Request) (*http.Response, error) {\n\t\t\t\tcapturedRequest = r\n\t\t\t\treturn &http.Response{StatusCode: http.StatusOK}, nil\n\t\t\t})\n\n\t\t\treq, err := http.NewRequestWithContext(tc.requestContext, http.MethodGet, \"http://fake.example.com/api\", http.NoBody)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar transport http.RoundTripper = wrappedTransport\n\t\t\tif tc.expectError {\n\t\t\t\ttransport = nil // Force nil transport for error test\n\t\t\t}\n\n\t\t\ttr := RoundTripper{\n\t\t\t\tTransport: transport,\n\t\t\t\tAuths:     tc.auths,\n\t\t\t}\n\n\t\t\tresp, err := tr.RoundTrip(req)\n\n\t\t\tif tc.expectError {\n\t\t\t\tassert.Nil(t, resp)\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), \"no http.RoundTripper provided\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.NotNil(t, resp)\n\t\t\trequire.NotNil(t, capturedRequest, \"Request should have been captured\")\n\n\t\t\t// Perform assertions on captured request\n\t\t\tif tc.expectNoAuthHeader {\n\t\t\t\tassert.Empty(t, capturedRequest.Header.Get(\"Authorization\"))\n\t\t\t} else if len(tc.expectedHeaders) > 0 {\n\t\t\t\tauthHeaders := capturedRequest.Header[\"Authorization\"]\n\t\t\t\tassert.ElementsMatch(t, tc.expectedHeaders, authHeaders)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/cache/cache.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cache\n\nimport (\n\t\"time\"\n)\n\n// A Cache is a generalized interface to a cache.  See cache.LRU for a specific\n// implementation (bounded cache with LRU eviction)\ntype Cache interface {\n\t// Get retrieves an element based on a key, returning nil if the element\n\t// does not exist\n\tGet(key string) any\n\n\t// Put adds an element to the cache, returning the previous element\n\tPut(key string, value any) any\n\n\t// Delete deletes an element in the cache\n\tDelete(key string)\n\n\t// Size returns the number of entries currently stored in the Cache\n\tSize() int\n\n\t// CompareAndSwap adds an element to the cache if the existing entry matches the old value.\n\t// It returns the element in cache after function is executed and true if the element was replaced, false otherwise.\n\tCompareAndSwap(key string, oldEntry, newEntry any) (any, bool)\n}\n\n// Options control the behavior of the cache\ntype Options struct {\n\t// TTL controls the time-to-live for a given cache entry.  Cache entries that\n\t// are older than the TTL will not be returned\n\tTTL time.Duration\n\n\t// InitialCapacity controls the initial capacity of the cache\n\tInitialCapacity int\n\n\t// OnEvict is an optional function called when an element is evicted.\n\tOnEvict EvictCallback\n\n\t// TimeNow is used to override the behavior of default time.Now(), e.g. in tests.\n\tTimeNow func() time.Time\n}\n\n// EvictCallback is a type for notifying applications when an item is\n// scheduled for eviction from the Cache.\ntype EvictCallback func(key string, value any)\n"
  },
  {
    "path": "internal/cache/lru.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cache\n\nimport (\n\t\"container/list\"\n\t\"sync\"\n\t\"time\"\n)\n\n// LRU is a concurrent fixed size cache that evicts elements in LRU order as well as by TTL.\ntype LRU struct {\n\tmux      sync.Mutex\n\tbyAccess *list.List\n\tbyKey    map[string]*list.Element\n\tmaxSize  int\n\tttl      time.Duration\n\tTimeNow  func() time.Time\n\tonEvict  EvictCallback\n}\n\n// NewLRUWithOptions creates a new LRU cache with the given options.\nfunc NewLRUWithOptions(maxSize int, opts *Options) *LRU {\n\tif opts == nil {\n\t\topts = &Options{}\n\t}\n\tif opts.TimeNow == nil {\n\t\topts.TimeNow = time.Now\n\t}\n\treturn &LRU{\n\t\tbyAccess: list.New(),\n\t\tbyKey:    make(map[string]*list.Element, opts.InitialCapacity),\n\t\tttl:      opts.TTL,\n\t\tmaxSize:  maxSize,\n\t\tTimeNow:  opts.TimeNow,\n\t\tonEvict:  opts.OnEvict,\n\t}\n}\n\n// Get retrieves the value stored under the given key\nfunc (c *LRU) Get(key string) any {\n\tc.mux.Lock()\n\tdefer c.mux.Unlock()\n\n\telt := c.byKey[key]\n\tif elt == nil {\n\t\treturn nil\n\t}\n\n\tcacheEntry := elt.Value.(*cacheEntry)\n\tif !cacheEntry.expiration.IsZero() && c.TimeNow().After(cacheEntry.expiration) {\n\t\t// Entry has expired\n\t\tif c.onEvict != nil {\n\t\t\tc.onEvict(cacheEntry.key, cacheEntry.value)\n\t\t}\n\t\tc.byAccess.Remove(elt)\n\t\tdelete(c.byKey, cacheEntry.key)\n\t\treturn nil\n\t}\n\n\tc.byAccess.MoveToFront(elt)\n\treturn cacheEntry.value\n}\n\n// Put puts a new value associated with a given key, returning the existing value (if present)\nfunc (c *LRU) Put(key string, value any) any {\n\tc.mux.Lock()\n\tdefer c.mux.Unlock()\n\telt := c.byKey[key]\n\treturn c.putWithMutexHold(key, value, elt)\n}\n\n// CompareAndSwap puts a new value associated with a given key if existing value matches oldValue.\n// It returns itemInCache as the element in cache after the function is executed and replaced as true if value is replaced, false otherwise.\nfunc (c *LRU) CompareAndSwap(key string, oldValue, newValue any) (itemInCache any, replaced bool) {\n\tc.mux.Lock()\n\tdefer c.mux.Unlock()\n\n\telt := c.byKey[key]\n\t// If entry not found, old value should be nil\n\tif elt == nil && oldValue != nil {\n\t\treturn nil, false\n\t}\n\n\tif elt != nil {\n\t\t// Entry found, compare it with that you expect.\n\t\tentry := elt.Value.(*cacheEntry)\n\t\tif entry.value != oldValue {\n\t\t\treturn entry.value, false\n\t\t}\n\t}\n\tc.putWithMutexHold(key, newValue, elt)\n\treturn newValue, true\n}\n\n// putWithMutexHold populates the cache and returns the inserted value.\n// Caller is expected to hold the c.mut mutex before calling.\nfunc (c *LRU) putWithMutexHold(key string, value any, elt *list.Element) any {\n\tif elt != nil {\n\t\tentry := elt.Value.(*cacheEntry)\n\t\texisting := entry.value\n\t\tentry.value = value\n\t\tif c.ttl != 0 {\n\t\t\tentry.expiration = c.TimeNow().Add(c.ttl)\n\t\t}\n\t\tc.byAccess.MoveToFront(elt)\n\t\treturn existing\n\t}\n\n\tentry := &cacheEntry{\n\t\tkey:   key,\n\t\tvalue: value,\n\t}\n\n\tif c.ttl != 0 {\n\t\tentry.expiration = c.TimeNow().Add(c.ttl)\n\t}\n\tc.byKey[key] = c.byAccess.PushFront(entry)\n\tfor len(c.byKey) > c.maxSize {\n\t\toldest := c.byAccess.Remove(c.byAccess.Back()).(*cacheEntry)\n\t\tif c.onEvict != nil {\n\t\t\tc.onEvict(oldest.key, oldest.value)\n\t\t}\n\t\tdelete(c.byKey, oldest.key)\n\t}\n\n\treturn nil\n}\n\n// Delete deletes a key, value pair associated with a key\nfunc (c *LRU) Delete(key string) {\n\tc.mux.Lock()\n\tdefer c.mux.Unlock()\n\n\telt := c.byKey[key]\n\tif elt != nil {\n\t\tentry := c.byAccess.Remove(elt).(*cacheEntry)\n\t\tif c.onEvict != nil {\n\t\t\tc.onEvict(entry.key, entry.value)\n\t\t}\n\t\tdelete(c.byKey, key)\n\t}\n}\n\n// Size returns the number of entries currently in the lru, useful if cache is not full\nfunc (c *LRU) Size() int {\n\tc.mux.Lock()\n\tdefer c.mux.Unlock()\n\n\treturn len(c.byKey)\n}\n\ntype cacheEntry struct {\n\tkey        string\n\texpiration time.Time\n\tvalue      any\n}\n"
  },
  {
    "path": "internal/cache/lru_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cache\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestLRU(t *testing.T) {\n\tcache := NewLRUWithOptions(4, &Options{\n\t\tOnEvict: func(_ string, _ any) {\n\t\t\t// do nothing, just for code coverage\n\t\t},\n\t})\n\n\tcache.Put(\"A\", \"Foo\")\n\tassert.Equal(t, \"Foo\", cache.Get(\"A\"))\n\tassert.Nil(t, cache.Get(\"B\"))\n\tassert.Equal(t, 1, cache.Size())\n\n\tcache.Put(\"B\", \"Bar\")\n\tcache.Put(\"C\", \"Cid\")\n\tcache.Put(\"D\", \"Delt\")\n\tassert.Equal(t, 4, cache.Size())\n\n\tassert.Equal(t, \"Bar\", cache.Get(\"B\"))\n\tassert.Equal(t, \"Cid\", cache.Get(\"C\"))\n\tassert.Equal(t, \"Delt\", cache.Get(\"D\"))\n\n\tcache.Put(\"A\", \"Foo2\")\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\n\tcache.Put(\"E\", \"Epsi\")\n\tassert.Equal(t, \"Epsi\", cache.Get(\"E\"))\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\tassert.Nil(t, cache.Get(\"B\")) // Oldest, should be evicted\n\n\t// Access C, D is now LRU\n\tcache.Get(\"C\")\n\tcache.Put(\"F\", \"Felp\")\n\tassert.Nil(t, cache.Get(\"D\"))\n\n\tcache.Delete(\"A\")\n\tassert.Nil(t, cache.Get(\"A\"))\n}\n\nfunc TestCompareAndSwap(t *testing.T) {\n\tcache := NewLRUWithOptions(2, nil)\n\n\titem, ok := cache.CompareAndSwap(\"A\", nil, \"Foo\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"Foo\", item)\n\tassert.Equal(t, \"Foo\", cache.Get(\"A\"))\n\tassert.Nil(t, cache.Get(\"B\"))\n\tassert.Equal(t, 1, cache.Size())\n\n\titem, ok = cache.CompareAndSwap(\"B\", nil, \"Bar\")\n\tassert.True(t, ok)\n\tassert.Equal(t, 2, cache.Size())\n\tassert.Equal(t, \"Bar\", item)\n\tassert.Equal(t, \"Bar\", cache.Get(\"B\"))\n\n\titem, ok = cache.CompareAndSwap(\"A\", \"Foo\", \"Foo2\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"Foo2\", item)\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\n\titem, ok = cache.CompareAndSwap(\"A\", nil, \"Foo3\")\n\tassert.False(t, ok)\n\tassert.Equal(t, \"Foo2\", item)\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\n\titem, ok = cache.CompareAndSwap(\"A\", \"Foo\", \"Foo3\")\n\tassert.False(t, ok)\n\tassert.Equal(t, \"Foo2\", item)\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\n\titem, ok = cache.CompareAndSwap(\"F\", \"foo\", \"Foo3\")\n\tassert.False(t, ok)\n\tassert.Nil(t, item)\n\tassert.Nil(t, cache.Get(\"F\"))\n\n\t// Evict the oldest entry\n\titem, ok = cache.CompareAndSwap(\"E\", nil, \"Epsi\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"Epsi\", item)\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\tassert.Nil(t, cache.Get(\"B\")) // Oldest, should be evicted\n}\n\nfunc TestLRUWithTTL(t *testing.T) {\n\tclk := &simulatedClock{}\n\tcache := NewLRUWithOptions(5, &Options{\n\t\tTTL:     time.Millisecond * 100,\n\t\tTimeNow: clk.Now,\n\t})\n\tcache.Put(\"A\", \"Foo\")\n\tassert.Equal(t, \"Foo\", cache.Get(\"A\"))\n\n\titem, _ := cache.CompareAndSwap(\"A\", \"Foo\", \"Foo2\")\n\tassert.Equal(t, \"Foo2\", item)\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\n\tclk.Elapse(time.Millisecond * 50)\n\tassert.Equal(t, \"Foo2\", cache.Get(\"A\"))\n\n\tclk.Elapse(time.Millisecond * 100)\n\tassert.Nil(t, cache.Get(\"A\"))\n\tassert.Equal(t, 0, cache.Size())\n}\n\nfunc TestDefaultClock(t *testing.T) {\n\tcache := NewLRUWithOptions(5, &Options{\n\t\tTTL: time.Millisecond * 1,\n\t})\n\tcache.Put(\"A\", \"foo\")\n\tassert.Equal(t, \"foo\", cache.Get(\"A\"))\n\ttime.Sleep(time.Millisecond * 3)\n\tassert.Nil(t, cache.Get(\"A\"))\n\tassert.Equal(t, 0, cache.Size())\n}\n\nfunc TestLRUCacheConcurrentAccess(*testing.T) {\n\tcache := NewLRUWithOptions(5, nil)\n\tvalues := map[string]string{\n\t\t\"A\": \"foo\",\n\t\t\"B\": \"bar\",\n\t\t\"C\": \"zed\",\n\t\t\"D\": \"dank\",\n\t\t\"E\": \"ezpz\",\n\t}\n\n\tfor k, v := range values {\n\t\tcache.Put(k, v)\n\t}\n\n\tstart := make(chan struct{})\n\tvar wg sync.WaitGroup\n\tfor range 20 {\n\t\twg.Go(func() {\n\t\t\t<-start\n\n\t\t\tfor range 1000 {\n\t\t\t\tcache.Get(\"A\")\n\t\t\t}\n\t\t})\n\t}\n\n\tclose(start)\n\twg.Wait()\n}\n\nfunc TestRemoveFunc(t *testing.T) {\n\tch := make(chan bool)\n\tcache := NewLRUWithOptions(5, &Options{\n\t\tOnEvict: func(_ string, i any) {\n\t\t\tgo func() {\n\t\t\t\t_, ok := i.(*testing.T)\n\t\t\t\tassert.True(t, ok)\n\t\t\t\tch <- true\n\t\t\t}()\n\t\t},\n\t})\n\n\tcache.Put(\"testing\", t)\n\tcache.Delete(\"testing\")\n\tassert.Nil(t, cache.Get(\"testing\"))\n\n\ttimeout := time.NewTimer(time.Millisecond * 300)\n\tselect {\n\tcase b := <-ch:\n\t\tassert.True(t, b)\n\tcase <-timeout.C:\n\t\tt.Error(\"RemovedFunc did not send true on channel ch\")\n\t}\n}\n\nfunc TestRemovedFuncWithTTL(t *testing.T) {\n\tch := make(chan bool)\n\tcache := NewLRUWithOptions(5, &Options{\n\t\tTTL: time.Millisecond * 5,\n\t\tOnEvict: func(_ string, i any) {\n\t\t\tgo func() {\n\t\t\t\t_, ok := i.(*testing.T)\n\t\t\t\tassert.True(t, ok)\n\t\t\t\tch <- true\n\t\t\t}()\n\t\t},\n\t})\n\n\tcache.Put(\"A\", t)\n\tassert.Equal(t, t, cache.Get(\"A\"))\n\ttime.Sleep(time.Millisecond * 10)\n\tassert.Nil(t, cache.Get(\"A\"))\n\n\ttimeout := time.NewTimer(time.Millisecond * 30)\n\tselect {\n\tcase b := <-ch:\n\t\tassert.True(t, b)\n\tcase <-timeout.C:\n\t\tt.Error(\"RemovedFunc did not send true on channel ch\")\n\t}\n}\n\ntype simulatedClock struct {\n\tsync.Mutex\n\tcurrTime time.Time\n}\n\nfunc (c *simulatedClock) Now() time.Time {\n\tc.Lock()\n\tdefer c.Unlock()\n\treturn c.currTime\n}\n\nfunc (c *simulatedClock) Elapse(d time.Duration) time.Time {\n\tc.Lock()\n\tdefer c.Unlock()\n\tc.currTime = c.currTime.Add(d)\n\treturn c.currTime\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/config/config.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"flag\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\n// Viperize creates new Viper and command add passes flags to command\n// Viper is initialized with flags from command and configured to accept flags as environmental variables.\n// Characters `.-` in environmental variables are changed to `_`\nfunc Viperize(inits ...func(*flag.FlagSet)) (*viper.Viper, *cobra.Command) {\n\treturn AddFlags(viper.New(), &cobra.Command{}, inits...)\n}\n\n// AddFlags adds flags to command and viper and configures\nfunc AddFlags(v *viper.Viper, command *cobra.Command, inits ...func(*flag.FlagSet)) (*viper.Viper, *cobra.Command) {\n\tflagSet := new(flag.FlagSet)\n\tfor i := range inits {\n\t\tinits[i](flagSet)\n\t}\n\tcommand.Flags().AddGoFlagSet(flagSet)\n\n\tconfigureViper(v)\n\tv.BindPFlags(command.Flags())\n\treturn v, command\n}\n\nfunc configureViper(v *viper.Viper) {\n\tv.AutomaticEnv()\n\tv.SetEnvKeyReplacer(strings.NewReplacer(\"-\", \"_\", \".\", \"_\"))\n}\n"
  },
  {
    "path": "internal/config/config_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestViperize(t *testing.T) {\n\tintFlag := \"intFlag\"\n\tstringFlag := \"stringFlag\"\n\tdurationFlag := \"durationFlag\"\n\n\texpectedInt := 5\n\texpectedString := \"string\"\n\texpectedDuration := 13 * time.Second\n\n\taddFlags := func(flagSet *flag.FlagSet) {\n\t\tflagSet.Int(intFlag, 0, \"\")\n\t\tflagSet.String(stringFlag, \"\", \"\")\n\t\tflagSet.Duration(durationFlag, 0, \"\")\n\t}\n\n\tv, command := Viperize(addFlags)\n\tcommand.ParseFlags([]string{\n\t\tfmt.Sprintf(\"--%s=%d\", intFlag, expectedInt),\n\t\tfmt.Sprintf(\"--%s=%s\", stringFlag, expectedString),\n\t\tfmt.Sprintf(\"--%s=%s\", durationFlag, expectedDuration.String()),\n\t})\n\n\tassert.Equal(t, expectedInt, v.GetInt(intFlag))\n\tassert.Equal(t, expectedString, v.GetString(stringFlag))\n\tassert.Equal(t, expectedDuration, v.GetDuration(durationFlag))\n}\n\nfunc TestEnv(t *testing.T) {\n\tenvFlag := \"jaeger.test-flag\"\n\tactualEnvFlag := \"JAEGER_TEST_FLAG\"\n\n\taddFlags := func(flagSet *flag.FlagSet) {\n\t\tflagSet.String(envFlag, \"\", \"\")\n\t}\n\texpectedString := \"string\"\n\tt.Setenv(actualEnvFlag, expectedString)\n\n\tv, _ := Viperize(addFlags)\n\tassert.Equal(t, expectedString, v.GetString(envFlag))\n}\n"
  },
  {
    "path": "internal/config/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/config/promcfg/config.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage promcfg\n\nimport (\n\t\"time\"\n\n\t\"github.com/asaskevich/govalidator\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n)\n\n// Configuration describes the options to customize the storage behavior.\ntype Configuration struct {\n\tServerURL      string        `valid:\"required\" mapstructure:\"endpoint\"`\n\tConnectTimeout time.Duration `mapstructure:\"connect_timeout\"`\n\t// TLS contains the TLS configuration for the connection to the Prometheus clusters.\n\tTLS configtls.ClientConfig `mapstructure:\"tls\"`\n\n\tTokenFilePath            string `mapstructure:\"token_file_path\"`\n\tTokenOverrideFromContext bool   `mapstructure:\"token_override_from_context\"`\n\n\tMetricNamespace   string `mapstructure:\"metric_namespace\"`\n\tLatencyUnit       string `mapstructure:\"latency_unit\"`\n\tNormalizeCalls    bool   `mapstructure:\"normalize_calls\"`\n\tNormalizeDuration bool   `mapstructure:\"normalize_duration\"`\n\t// ExtraQueryParams is used to provide extra parameters to be appended\n\t// to the URL of queries going out to the metrics backend.\n\tExtraQueryParams map[string]string `mapstructure:\"extra_query_parameters\"`\n}\n\nfunc (c *Configuration) Validate() error {\n\t_, err := govalidator.ValidateStruct(c)\n\treturn err\n}\n"
  },
  {
    "path": "internal/config/promcfg/config_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage promcfg\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestValidate(t *testing.T) {\n\tcfg := Configuration{\n\t\tServerURL: \"localhost:1234\",\n\t}\n\terr := cfg.Validate()\n\trequire.NoError(t, err)\n\tcfg = Configuration{}\n\terr = cfg.Validate()\n\trequire.Error(t, err)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/config/string_slice.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport \"strings\"\n\n// StringSlice implements the pflag.Value interface and allows for parsing multiple\n// config values with the same name. It purposefully mimics pFlag.stringSliceValue\n// (https://github.com/spf13/pflag/blob/master/string_slice.go) in order to be\n// treated like a string slice by both viper and pflag cleanly.\ntype StringSlice []string\n\n// String implements pflag.Value\nfunc (l *StringSlice) String() string {\n\tif len(*l) == 0 {\n\t\treturn \"[]\"\n\t}\n\n\treturn `[\"` + strings.Join(*l, `\",\"`) + `\"]`\n}\n\n// Set implements pflag.Value\nfunc (l *StringSlice) Set(value string) error {\n\t*l = append(*l, value)\n\treturn nil\n}\n\n// Type implements pflag.Value\nfunc (*StringSlice) Type() string {\n\t// this type string needs to match pflag.stringSliceValue's Type\n\treturn \"stringSlice\"\n}\n"
  },
  {
    "path": "internal/config/string_slice_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/pflag\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStringSlice(t *testing.T) {\n\tf := &StringSlice{}\n\n\tassert.Equal(t, \"[]\", f.String())\n\tassert.Equal(t, \"stringSlice\", f.Type())\n\n\tf.Set(\"test\")\n\tassert.Equal(t, `[\"test\"]`, f.String())\n\n\tf.Set(\"test2\")\n\tassert.Equal(t, `[\"test\",\"test2\"]`, f.String())\n\n\tf.Set(\"test3,test4\")\n\tassert.Equal(t, `[\"test\",\"test2\",\"test3,test4\"]`, f.String())\n}\n\nfunc TestStringSliceTreatedAsStringSlice(t *testing.T) {\n\tf := &StringSlice{}\n\n\t// create and add flags/values to a go flag set\n\tflagset := flag.NewFlagSet(\"test\", flag.ContinueOnError)\n\tflagset.Var(f, \"test\", \"test\")\n\n\terr := flagset.Set(\"test\", \"asdf\")\n\trequire.NoError(t, err)\n\terr = flagset.Set(\"test\", \"blerg\")\n\trequire.NoError(t, err)\n\terr = flagset.Set(\"test\", \"other,thing\")\n\trequire.NoError(t, err)\n\n\t// add go flag set to pflag\n\tpflagset := pflag.FlagSet{}\n\tpflagset.AddGoFlagSet(flagset)\n\tactual, err := pflagset.GetStringSlice(\"test\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, []string{\"asdf\", \"blerg\", \"other,thing\"}, actual)\n}\n"
  },
  {
    "path": "internal/config/tlscfg/flags.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tlscfg\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n)\n\nconst (\n\ttlsPrefix         = \".tls\"\n\ttlsEnabled        = tlsPrefix + \".enabled\"\n\ttlsCA             = tlsPrefix + \".ca\"\n\ttlsCert           = tlsPrefix + \".cert\"\n\ttlsKey            = tlsPrefix + \".key\"\n\ttlsServerName     = tlsPrefix + \".server-name\"\n\ttlsClientCA       = tlsPrefix + \".client-ca\"\n\ttlsSkipHostVerify = tlsPrefix + \".skip-host-verify\"\n\ttlsCipherSuites   = tlsPrefix + \".cipher-suites\"\n\ttlsMinVersion     = tlsPrefix + \".min-version\"\n\ttlsMaxVersion     = tlsPrefix + \".max-version\"\n\ttlsReloadInterval = tlsPrefix + \".reload-interval\"\n)\n\n// ClientFlagsConfig describes which CLI flags for TLS client should be generated.\ntype ClientFlagsConfig struct {\n\tPrefix string\n}\n\n// ServerFlagsConfig describes which CLI flags for TLS server should be generated.\ntype ServerFlagsConfig struct {\n\tPrefix string\n}\n\n// AddFlags adds flags for TLS to the FlagSet.\nfunc (c ClientFlagsConfig) AddFlags(flags *flag.FlagSet) {\n\tflags.Bool(c.Prefix+tlsEnabled, false, \"Enable TLS when talking to the remote server(s)\")\n\tflags.String(c.Prefix+tlsCA, \"\", \"Path to a TLS CA (Certification Authority) file used to verify the remote server(s) (by default will use the system truststore)\")\n\tflags.String(c.Prefix+tlsCert, \"\", \"Path to a TLS Certificate file, used to identify this process to the remote server(s)\")\n\tflags.String(c.Prefix+tlsKey, \"\", \"Path to a TLS Private Key file, used to identify this process to the remote server(s)\")\n\tflags.String(c.Prefix+tlsServerName, \"\", \"Override the TLS server name we expect in the certificate of the remote server(s)\")\n\tflags.Bool(c.Prefix+tlsSkipHostVerify, false, \"(insecure) Skip server's certificate chain and host name verification\")\n}\n\n// AddFlags adds flags for TLS to the FlagSet.\nfunc (c ServerFlagsConfig) AddFlags(flags *flag.FlagSet) {\n\tflags.Bool(c.Prefix+tlsEnabled, false, \"Enable TLS on the server\")\n\tflags.String(c.Prefix+tlsCert, \"\", \"Path to a TLS Certificate file, used to identify this server to clients\")\n\tflags.String(c.Prefix+tlsKey, \"\", \"Path to a TLS Private Key file, used to identify this server to clients\")\n\tflags.String(c.Prefix+tlsClientCA, \"\", \"Path to a TLS CA (Certification Authority) file used to verify certificates presented by clients (if unset, all clients are permitted)\")\n\tflags.String(c.Prefix+tlsCipherSuites, \"\", \"Comma-separated list of cipher suites for the server, values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).\")\n\tflags.String(c.Prefix+tlsMinVersion, \"\", \"Minimum TLS version supported (Possible values: 1.0, 1.1, 1.2, 1.3)\")\n\tflags.String(c.Prefix+tlsMaxVersion, \"\", \"Maximum TLS version supported (Possible values: 1.0, 1.1, 1.2, 1.3)\")\n\tflags.Duration(c.Prefix+tlsReloadInterval, 0, \"The duration after which the certificate will be reloaded (0s means will not be reloaded)\")\n}\n\n// InitFromViper creates tls.Config populated with values retrieved from Viper.\nfunc (c ClientFlagsConfig) InitFromViper(v *viper.Viper) (configtls.ClientConfig, error) {\n\tvar p options\n\tp.Enabled = v.GetBool(c.Prefix + tlsEnabled)\n\tp.CAPath = v.GetString(c.Prefix + tlsCA)\n\tp.CertPath = v.GetString(c.Prefix + tlsCert)\n\tp.KeyPath = v.GetString(c.Prefix + tlsKey)\n\tp.ServerName = v.GetString(c.Prefix + tlsServerName)\n\tp.SkipHostVerify = v.GetBool(c.Prefix + tlsSkipHostVerify)\n\n\tif !p.Enabled {\n\t\tvar empty options\n\t\tif !reflect.DeepEqual(&p, &empty) {\n\t\t\treturn configtls.ClientConfig{}, fmt.Errorf(\"%s.tls.* options cannot be used when %s is false\", c.Prefix, c.Prefix+tlsEnabled)\n\t\t}\n\t}\n\n\treturn p.ToOtelClientConfig(), nil\n}\n\n// InitFromViper creates tls.Config populated with values retrieved from Viper.\nfunc (c ServerFlagsConfig) InitFromViper(v *viper.Viper) (configoptional.Optional[configtls.ServerConfig], error) {\n\tvar p options\n\tp.Enabled = v.GetBool(c.Prefix + tlsEnabled)\n\tp.CertPath = v.GetString(c.Prefix + tlsCert)\n\tp.KeyPath = v.GetString(c.Prefix + tlsKey)\n\tp.ClientCAPath = v.GetString(c.Prefix + tlsClientCA)\n\tif s := v.GetString(c.Prefix + tlsCipherSuites); s != \"\" {\n\t\tp.CipherSuites = strings.Split(stripWhiteSpace(v.GetString(c.Prefix+tlsCipherSuites)), \",\")\n\t}\n\tp.MinVersion = v.GetString(c.Prefix + tlsMinVersion)\n\tp.MaxVersion = v.GetString(c.Prefix + tlsMaxVersion)\n\tp.ReloadInterval = v.GetDuration(c.Prefix + tlsReloadInterval)\n\n\tif !p.Enabled {\n\t\tvar empty options\n\t\tif !reflect.DeepEqual(&p, &empty) {\n\t\t\treturn configoptional.None[configtls.ServerConfig](), fmt.Errorf(\"%s.tls.* options cannot be used when %s is false\", c.Prefix, c.Prefix+tlsEnabled)\n\t\t}\n\t}\n\n\treturn p.ToOtelServerConfig(), nil\n}\n\n// stripWhiteSpace removes all whitespace characters from a string\nfunc stripWhiteSpace(str string) string {\n\treturn strings.ReplaceAll(str, \" \", \"\")\n}\n"
  },
  {
    "path": "internal/config/tlscfg/flags_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tlscfg\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config\"\n)\n\nfunc TestClientFlags(t *testing.T) {\n\tcmdLine := []string{\n\t\t\"--prefix.tls.ca=ca-file\",\n\t\t\"--prefix.tls.cert=cert-file\",\n\t\t\"--prefix.tls.key=key-file\",\n\t\t\"--prefix.tls.server-name=HAL1\",\n\t\t\"--prefix.tls.skip-host-verify=true\",\n\t}\n\n\ttests := []struct {\n\t\toption string\n\t}{\n\t\t{\n\t\t\toption: \"--prefix.tls.enabled=true\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.option, func(t *testing.T) {\n\t\t\tv := viper.New()\n\t\t\tcommand := cobra.Command{}\n\t\t\tflagSet := &flag.FlagSet{}\n\t\t\tflagCfg := ClientFlagsConfig{\n\t\t\t\tPrefix: \"prefix\",\n\t\t\t}\n\t\t\tflagCfg.AddFlags(flagSet)\n\t\t\tcommand.PersistentFlags().AddGoFlagSet(flagSet)\n\t\t\tv.BindPFlags(command.PersistentFlags())\n\n\t\t\terr := command.ParseFlags(append(cmdLine, test.option))\n\t\t\trequire.NoError(t, err)\n\t\t\tclientCfg, err := flagCfg.InitFromViper(v)\n\t\t\trequire.NoError(t, err)\n\n\t\t\texpectedCfg := configtls.ClientConfig{\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCAFile:                   \"ca-file\",\n\t\t\t\t\tCertFile:                 \"cert-file\",\n\t\t\t\t\tKeyFile:                  \"key-file\",\n\t\t\t\t\tIncludeSystemCACertsPool: false,\n\t\t\t\t\tCipherSuites:             nil,\n\t\t\t\t\tReloadInterval:           0,\n\t\t\t\t},\n\t\t\t\tInsecure:           false,\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\tServerName:         \"HAL1\",\n\t\t\t}\n\n\t\t\tassert.Equal(t, expectedCfg, clientCfg)\n\t\t})\n\t}\n}\n\nfunc TestServerFlags(t *testing.T) {\n\tcmdLine := []string{\n\t\t\"##placeholder##\", // replaced in each test below\n\t\t\"--prefix.tls.enabled=true\",\n\t\t\"--prefix.tls.cert=cert-file\",\n\t\t\"--prefix.tls.key=key-file\",\n\t\t\"--prefix.tls.cipher-suites=TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\",\n\t\t\"--prefix.tls.min-version=1.2\",\n\t\t\"--prefix.tls.max-version=1.3\",\n\t}\n\n\ttests := []struct {\n\t\toption string\n\t\tfile   string\n\t}{\n\t\t{\n\t\t\toption: \"--prefix.tls.client-ca=client-ca-file\",\n\t\t\tfile:   \"client-ca-file\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.file, func(t *testing.T) {\n\t\t\tv := viper.New()\n\t\t\tcommand := cobra.Command{}\n\t\t\tflagSet := &flag.FlagSet{}\n\t\t\tflagCfg := ServerFlagsConfig{\n\t\t\t\tPrefix: \"prefix\",\n\t\t\t}\n\t\t\tflagCfg.AddFlags(flagSet)\n\t\t\tcommand.PersistentFlags().AddGoFlagSet(flagSet)\n\t\t\tv.BindPFlags(command.PersistentFlags())\n\n\t\t\tcmdLine[0] = test.option\n\t\t\terr := command.ParseFlags(cmdLine)\n\t\t\trequire.NoError(t, err)\n\t\t\tserverConfig, err := flagCfg.InitFromViper(v)\n\t\t\trequire.NoError(t, err)\n\n\t\t\texpectedConfig := configoptional.Some(configtls.ServerConfig{\n\t\t\t\tClientCAFile: \"client-ca-file\",\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCertFile:     \"cert-file\",\n\t\t\t\t\tKeyFile:      \"key-file\",\n\t\t\t\t\tMinVersion:   \"1.2\",\n\t\t\t\t\tMaxVersion:   \"1.3\",\n\t\t\t\t\tCipherSuites: []string{\"TLS_AES_256_GCM_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\"},\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tassert.Equal(t, expectedConfig, serverConfig)\n\t\t})\n\t}\n}\n\nfunc TestServerCertReloadInterval(t *testing.T) {\n\tcfg := ServerFlagsConfig{\n\t\tPrefix: \"prefix\",\n\t}\n\tv, command := config.Viperize(cfg.AddFlags)\n\terr := command.ParseFlags([]string{\n\t\t\"--prefix.tls.enabled=true\",\n\t\t\"--prefix.tls.reload-interval=24h\",\n\t})\n\trequire.NoError(t, err)\n\ttlscfg, err := cfg.InitFromViper(v)\n\trequire.NoError(t, err)\n\trequire.True(t, tlscfg.HasValue())\n\tassert.Equal(t, 24*time.Hour, tlscfg.Get().ReloadInterval)\n}\n\n// TestFailedTLSFlags verifies that TLS options cannot be used when tls.enabled=false\nfunc TestFailedTLSFlags(t *testing.T) {\n\tclientTests := []string{\n\t\t\".ca=blah\",\n\t\t\".cert=blah\",\n\t\t\".key=blah\",\n\t\t\".server-name=blah\",\n\t\t\".skip-host-verify=true\",\n\t}\n\tserverTests := []string{\n\t\t\".cert=blah\",\n\t\t\".key=blah\",\n\t\t\".client-ca=blah\",\n\t\t\".cipher-suites=blah\",\n\t\t\".min-version=1.1\",\n\t\t\".max-version=1.3\",\n\t}\n\n\tallTests := []struct {\n\t\tside  string\n\t\ttests []string\n\t}{\n\t\t{side: \"client\", tests: clientTests},\n\t\t{side: \"server\", tests: serverTests},\n\t}\n\n\tfor _, metaTest := range allTests {\n\t\tt.Run(metaTest.side, func(t *testing.T) {\n\t\t\tfor _, test := range metaTest.tests {\n\t\t\t\tt.Run(test, func(t *testing.T) {\n\t\t\t\t\tvar (\n\t\t\t\t\t\taddFlags      func(*flag.FlagSet)\n\t\t\t\t\t\tinitFromViper func(*viper.Viper) (any, error)\n\t\t\t\t\t)\n\n\t\t\t\t\tif metaTest.side == \"client\" {\n\t\t\t\t\t\tclientConfig := &ClientFlagsConfig{Prefix: \"prefix\"}\n\t\t\t\t\t\taddFlags = clientConfig.AddFlags\n\t\t\t\t\t\tinitFromViper = func(v *viper.Viper) (any, error) {\n\t\t\t\t\t\t\treturn clientConfig.InitFromViper(v)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tserverConfig := &ServerFlagsConfig{Prefix: \"prefix\"}\n\t\t\t\t\t\taddFlags = serverConfig.AddFlags\n\t\t\t\t\t\tinitFromViper = func(v *viper.Viper) (any, error) {\n\t\t\t\t\t\t\treturn serverConfig.InitFromViper(v)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tv, command := config.Viperize(addFlags)\n\n\t\t\t\t\tcmdLine := []string{\n\t\t\t\t\t\t\"--prefix.tls.enabled=true\",\n\t\t\t\t\t\t\"--prefix.tls\" + test,\n\t\t\t\t\t}\n\t\t\t\t\terr := command.ParseFlags(cmdLine)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tresult, err := initFromViper(v)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\tswitch metaTest.side {\n\t\t\t\t\tcase \"client\":\n\t\t\t\t\t\trequire.IsType(t, configtls.ClientConfig{}, result, \"result should be of type configtls.ClientConfig\")\n\t\t\t\t\tcase \"server\":\n\t\t\t\t\t\texp := configoptional.Some(configtls.ServerConfig{})\n\t\t\t\t\t\trequire.IsType(t, exp, result, \"result should be of type *configtls.ServerConfig\")\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tt.Errorf(\"Unexpected side value: %s\", metaTest.side)\n\t\t\t\t\t}\n\n\t\t\t\t\tcmdLine[0] = \"--prefix.tls.enabled=false\"\n\t\t\t\t\terr = command.ParseFlags(cmdLine)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\t_, err = initFromViper(v)\n\t\t\t\t\trequire.Error(t, err)\n\t\t\t\t\trequire.EqualError(t, err, \"prefix.tls.* options cannot be used when prefix.tls.enabled is false\")\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/config/tlscfg/options.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tlscfg\n\nimport (\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n)\n\n// options describes the configuration properties for TLS Connections.\ntype options struct {\n\tEnabled        bool\n\tCAPath         string\n\tCertPath       string\n\tKeyPath        string\n\tServerName     string // only for client-side TLS config\n\tClientCAPath   string // only for server-side TLS config for client auth\n\tCipherSuites   []string\n\tMinVersion     string\n\tMaxVersion     string\n\tSkipHostVerify bool\n\tReloadInterval time.Duration\n}\n\nfunc (o *options) ToOtelClientConfig() configtls.ClientConfig {\n\treturn configtls.ClientConfig{\n\t\tInsecure:           !o.Enabled,\n\t\tInsecureSkipVerify: o.SkipHostVerify,\n\t\tServerName:         o.ServerName,\n\t\tConfig: configtls.Config{\n\t\t\tCAFile:         o.CAPath,\n\t\t\tCertFile:       o.CertPath,\n\t\t\tKeyFile:        o.KeyPath,\n\t\t\tCipherSuites:   o.CipherSuites,\n\t\t\tMinVersion:     o.MinVersion,\n\t\t\tMaxVersion:     o.MaxVersion,\n\t\t\tReloadInterval: o.ReloadInterval,\n\n\t\t\t// when no truststore given, use SystemCertPool\n\t\t\t// https://github.com/jaegertracing/jaeger/issues/6334\n\t\t\tIncludeSystemCACertsPool: o.Enabled && (o.CAPath == \"\"),\n\t\t},\n\t}\n}\n\n// ToOtelServerConfig provides a mapping between from Options to OTEL's TLS Server Configuration.\nfunc (o *options) ToOtelServerConfig() configoptional.Optional[configtls.ServerConfig] {\n\tif !o.Enabled {\n\t\treturn configoptional.None[configtls.ServerConfig]()\n\t}\n\n\tcfg := configtls.ServerConfig{\n\t\tClientCAFile: o.ClientCAPath,\n\t\tConfig: configtls.Config{\n\t\t\tCAFile:         o.CAPath,\n\t\t\tCertFile:       o.CertPath,\n\t\t\tKeyFile:        o.KeyPath,\n\t\t\tCipherSuites:   o.CipherSuites,\n\t\t\tMinVersion:     o.MinVersion,\n\t\t\tMaxVersion:     o.MaxVersion,\n\t\t\tReloadInterval: o.ReloadInterval,\n\t\t},\n\t}\n\n\tif o.ReloadInterval > 0 {\n\t\tcfg.ReloadClientCAFile = true\n\t}\n\n\treturn configoptional.Some(cfg)\n}\n"
  },
  {
    "path": "internal/config/tlscfg/options_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tlscfg\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n)\n\nfunc TestToOtelClientConfig(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\toptions  options\n\t\texpected configtls.ClientConfig\n\t}{\n\t\t{\n\t\t\tname: \"insecure\",\n\t\t\toptions: options{\n\t\t\t\tEnabled: false,\n\t\t\t},\n\t\t\texpected: configtls.ClientConfig{\n\t\t\t\tInsecure: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"secure with skip host verify\",\n\t\t\toptions: options{\n\t\t\t\tEnabled:        true,\n\t\t\t\tSkipHostVerify: true,\n\t\t\t\tServerName:     \"example.com\",\n\t\t\t\tCAPath:         \"path/to/ca.pem\",\n\t\t\t\tCertPath:       \"path/to/cert.pem\",\n\t\t\t\tKeyPath:        \"path/to/key.pem\",\n\t\t\t\tCipherSuites:   []string{\"TLS_RSA_WITH_AES_128_CBC_SHA\"},\n\t\t\t\tMinVersion:     \"1.2\",\n\t\t\t\tMaxVersion:     \"1.3\",\n\t\t\t\tReloadInterval: 24 * time.Hour,\n\t\t\t},\n\t\t\texpected: configtls.ClientConfig{\n\t\t\t\tInsecure:           false,\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\tServerName:         \"example.com\",\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCAFile:         \"path/to/ca.pem\",\n\t\t\t\t\tCertFile:       \"path/to/cert.pem\",\n\t\t\t\t\tKeyFile:        \"path/to/key.pem\",\n\t\t\t\t\tCipherSuites:   []string{\"TLS_RSA_WITH_AES_128_CBC_SHA\"},\n\t\t\t\t\tMinVersion:     \"1.2\",\n\t\t\t\t\tMaxVersion:     \"1.3\",\n\t\t\t\t\tReloadInterval: 24 * time.Hour,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactual := tc.options.ToOtelClientConfig()\n\t\t\tassert.Equal(t, tc.expected, actual)\n\t\t})\n\t}\n}\n\nfunc TestToOtelServerConfig(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\toptions  options\n\t\texpected configoptional.Optional[configtls.ServerConfig]\n\t}{\n\t\t{\n\t\t\tname: \"not enabled\",\n\t\t\toptions: options{\n\t\t\t\tEnabled: false,\n\t\t\t},\n\t\t\texpected: configoptional.None[configtls.ServerConfig](),\n\t\t},\n\t\t{\n\t\t\tname: \"default mapping\",\n\t\t\toptions: options{\n\t\t\t\tEnabled:      true,\n\t\t\t\tClientCAPath: \"path/to/client/ca.pem\",\n\t\t\t\tCAPath:       \"path/to/ca.pem\",\n\t\t\t\tCertPath:     \"path/to/cert.pem\",\n\t\t\t\tKeyPath:      \"path/to/key.pem\",\n\t\t\t\tCipherSuites: []string{\"TLS_RSA_WITH_AES_128_CBC_SHA\"},\n\t\t\t\tMinVersion:   \"1.2\",\n\t\t\t\tMaxVersion:   \"1.3\",\n\t\t\t},\n\t\t\texpected: configoptional.Some(configtls.ServerConfig{\n\t\t\t\tClientCAFile: \"path/to/client/ca.pem\",\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCAFile:       \"path/to/ca.pem\",\n\t\t\t\t\tCertFile:     \"path/to/cert.pem\",\n\t\t\t\t\tKeyFile:      \"path/to/key.pem\",\n\t\t\t\t\tCipherSuites: []string{\"TLS_RSA_WITH_AES_128_CBC_SHA\"},\n\t\t\t\t\tMinVersion:   \"1.2\",\n\t\t\t\t\tMaxVersion:   \"1.3\",\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tname: \"with reload interval\",\n\t\t\toptions: options{\n\t\t\t\tEnabled:        true,\n\t\t\t\tClientCAPath:   \"path/to/client/ca.pem\",\n\t\t\t\tCAPath:         \"path/to/ca.pem\",\n\t\t\t\tCertPath:       \"path/to/cert.pem\",\n\t\t\t\tKeyPath:        \"path/to/key.pem\",\n\t\t\t\tCipherSuites:   []string{\"TLS_RSA_WITH_AES_128_CBC_SHA\"},\n\t\t\t\tMinVersion:     \"1.2\",\n\t\t\t\tMaxVersion:     \"1.3\",\n\t\t\t\tReloadInterval: 24 * time.Hour,\n\t\t\t},\n\t\t\texpected: configoptional.Some(configtls.ServerConfig{\n\t\t\t\tClientCAFile:       \"path/to/client/ca.pem\",\n\t\t\t\tReloadClientCAFile: true,\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCAFile:         \"path/to/ca.pem\",\n\t\t\t\t\tCertFile:       \"path/to/cert.pem\",\n\t\t\t\t\tKeyFile:        \"path/to/key.pem\",\n\t\t\t\t\tCipherSuites:   []string{\"TLS_RSA_WITH_AES_128_CBC_SHA\"},\n\t\t\t\t\tMinVersion:     \"1.2\",\n\t\t\t\t\tMaxVersion:     \"1.3\",\n\t\t\t\t\tReloadInterval: 24 * time.Hour,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactual := tc.options.ToOtelServerConfig()\n\t\t\tassert.Equal(t, tc.expected, actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/config/tlscfg/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tlscfg\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/README.md",
    "content": "# Example Certificate Authority and Certificate creation for testing\n\nThe PEM files located in this directory are used by unit tests in this package.\n\nTo generate and update the PEM files in this directory, run the following from the project root:\n\n    make certs\n\nTo only generate the PEM files without copying them to this directory:\n\n    make certs-dryrun\n    \nThe location of the generated PEM files will be printed to STDOUT like so:\n\n    # Dry-run complete. Generated files can be found in /var/folders/3p/yms48z2s6v7c8fy2m_1481g00000gn/T/certificates.p7pFHXpy\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/bad-CA-cert.txt",
    "content": "-----BEGIN CERTIFICATE-----\nbad certificate\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/example-CA-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDMTCCAhkCFCi65dSe1JONpNGghyam61+4gTL7MA0GCSqGSIb3DQEBCwUAMFUx\nCzAJBgNVBAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5l\neTEQMA4GA1UECgwHTG9nei5pbzEPMA0GA1UEAwwGSmFlZ2VyMB4XDTIyMDkxMDAw\nMjE0NFoXDTMyMDkwNzAwMjE0NFowVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1\nc3RyYWxpYTEPMA0GA1UEBwwGU3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYD\nVQQDDAZKYWVnZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLlEq/\nDF2pkhfSedvAd5h6BXCjpC/mUA6BN3RyMHUjTWr9hhBtaIYv68O12GMVf//ST/Fs\nCjRrjOcqrz2QQn3P8UelGRd2vJfcMhJElQ/lnKmZZlAHEOMF8TC7nQfsReLCwcpj\nT6bXqvDcfHjDye+45F2rPDpRGLzyysg7pgdINp0Duph0Z16ggrBgz7RVNBmWsYVe\nsGD3VOR3hLd8GTDzJ5amRpkq8nfliJ+U3JLGcDG/7Wkuvl/YZZxf21v9f4yYVEZZ\naLAcKsHIUoFRDJtdrBeaPZRJjL/I9B1M6En+Styxb5wJw42h9BXtJd2IeQPp15pP\nKfPbkmOj+X+2s9n1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJbm7WXgQirWQbaa\nE304K8tvdpC2E1ewxTTrUEN8jUONER4KC+epRnsTgkEpVlj7sehiAgSMnbT4E3ve\nGjmsUrZiJcKPaf+ogn49Cj0weD99wbJtUNgbH4HiqR1ePOHIRDQ7GD5G0zdFq7oO\nIl09eHAbbWM61x04I3XDQ0OwXyeVXIEWJcR1R6wnuNMJm54czbXvn6SrIuoMCvs6\noSkVm43Q+plk0hlDZnA/KiOxqFRLVHBuX/SgRf5NBg8m7id3fNzIJnWWK+zqoDoZ\nryja7dFIJnLqEXJxJkc5ubT1/j9PDE51WbM5MyPB6lnuQKdZTbDziyKiVXg0au3E\nQK5K/Ow=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/example-client-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDUjCCAjqgAwIBAgIUE56RLVss9rH/ojHQlVqysg6vJQYwDQYJKoZIhvcNAQEL\nBQAwVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3RyYWxpYTEPMA0GA1UEBwwG\nU3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYDVQQDDAZKYWVnZXIwHhcNMjIw\nOTEwMDAyMTQ0WhcNMzIwOTA3MDAyMTQ0WjBVMQswCQYDVQQGEwJBVTESMBAGA1UE\nCAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEDAOBgNVBAoMB0xvZ3ouaW8x\nDzANBgNVBAMMBkphZWdlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nALwEHBIe2nffXpJtYRUQ2GzuIwzPhI6fwT/KYXc/ao6mhHQ/oGyMZJFBxxpB0Inz\nGuurMtJBIgAlrhKmX5Dg5tB05iMpqA1Hbxa4fQS34iw9bBEvH/7SkuQ7gox6ht/n\nZX9UuyAw751B/KlGQlVInGzySAgR9T7RdT7YOAGoaQtXNsE6b3/Jm6z/uRW3Buqp\n1jzqL9VHzUdC7k8nRRdTTivcDUiZ+ocp4j2lRVP4hOylU0DSAG7mfwR8YQ/Xt1cU\nkn+2pe+D4tcx23lQcQFFWeJ2CKVx+Gx2BwJNoqPJ0LJLLSAQyY+S2wGSjoc8nqvM\n8mhgykFU9dW+GEwJzhLqRRMCAwEAAaMaMBgwFgYDVR0RBA8wDYILZXhhbXBsZS5j\nb20wDQYJKoZIhvcNAQELBQADggEBABqjQPg5voqMNnBBtnAKDnuTF4hOBNAo0Wq/\nKzD5QqvaWPPspx+oIahSIwq+8aL+NzptfYwbke10Q5qmOzq/ZgVTela+k/hgbjn6\nI/nOTCg5/v7m0AN3HgIGdgh5TOBiZMEsNpS+Lr2DangjaBKwpe4sucsgevJpggg1\nm/FT8rL7X5AjNx+mgsjdzQaboe6SkaGSSzByN8jEO03ceYpLvfqMGdAJpF4MEGiZ\nBlAAMHn3m5NLuBsHM/SiewTEmLBa6AEo33/XI0rOjDlYOj7A0xj2NLz0EwfRf3AG\nUpDuAB9O5n3iVXrHtaHDMRihGjBbeDEVaf68uodz7nH/UIWc2Rs=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/example-client-key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvAQcEh7ad99ekm1hFRDYbO4jDM+Ejp/BP8phdz9qjqaEdD+g\nbIxkkUHHGkHQifMa66sy0kEiACWuEqZfkODm0HTmIymoDUdvFrh9BLfiLD1sES8f\n/tKS5DuCjHqG3+dlf1S7IDDvnUH8qUZCVUicbPJICBH1PtF1Ptg4AahpC1c2wTpv\nf8mbrP+5FbcG6qnWPOov1UfNR0LuTydFF1NOK9wNSJn6hyniPaVFU/iE7KVTQNIA\nbuZ/BHxhD9e3VxSSf7al74Pi1zHbeVBxAUVZ4nYIpXH4bHYHAk2io8nQskstIBDJ\nj5LbAZKOhzyeq8zyaGDKQVT11b4YTAnOEupFEwIDAQABAoIBAEWCa3JTj8dDgG44\nG+0y1iCnhbPFwKcN7t8Lji8M9fMZItzrbP7UhJWjMN3HOTbW9rvsBhTvWYeeZpWk\nhq5ER3EH1tFnJCcMoshOmoG1Ddv3NU3BE14dMYtJaQFQhy6eGMsTYz8KeHu2Gpfm\nTr3C43nvtKuvH/ECdQsv2rzaK0OybxwN0GQGPPeKFhGJ8/v+s2NGQvfuaDFqPZ3u\n9e/gZ8DHAPrj6kf87j1SzDAytuaDItPwQJv01Kc9gVPajuoWMTkz1IcGB5KhRohO\nCO8h33PSA1gq7d7yn4fCqtwWORJTqFc7Bq1512bM4dh+lqhm+LAYPI7s899x2KI3\nA837xLkCgYEA6O/6nN2npwhPEROBL7T5KGKOwWrgKLO1XL/zwtrXAXFO4pN0Pvcf\nK5Aqrm1TjhDqwvMUmtjFVrB6FkejGw/22NlKNrsBQ5HQvHv8RjLu7kC5VxysUei1\nS7IlS8HjA6APap9cgELWocivrFIOijXXvRmO0EgIqWYl2TF60Cyx6sUCgYEAzqGO\nTR1IE8s2bJGxwF3toSR69Q3i5GgUnELATNEqIg0i/j9kYDNl8oJmyJ0DEsKlIe6X\n72JSDMLX0mwzBHit7LXvBUYAXCu/i91Rnkn7ME7KTPIJaKnZHNT1KZQi0vClv9St\ngQeAl1YGHSlEh98lEhHwykchmqaVFiOo0zlNzfcCgYEAwDHRvEB/JiiK5HINc4mE\n0zeOxjQixDKS//Y5cJsUL9KH3hcAITvRciY/sS/vcxauPTBH3gPhv0dZVKzC/X9M\nk1umCkZ+InxbmElMu7cmwVqSEjhMTkEN5WkVsM5HOySD09utfP6pDVAC8tG5wXvv\nh81gsqXcz7jCndRfmwhlvGkCgYEAhVOLDUj6jAMQX+d2aShyPwrZ56sJHtXljpon\nmKlR5VzSmnju3H/tpRftGD7vj7hWctmP4Z9wT9mdBqJYHOd9WgJecumjK9Xyp12r\n31XfJWGBeTqnRYhqlgb3FdgGzFMIsAmb1miv2XZhRYmuNXmPYuR+mRZioXYhNoLV\n2UzdXisCgYAofFoRrtFAUZn1AY7no1MSXrOZ0fAwnRm6va73aSOFHHJG9swI2GOi\nhGUuABh6TbpU6G7FIDD/E9zjXoz43j6muN9RUqynVK4x+fEUQDGUtHXavtL97vWg\neHbzNbxx46DlaGj2VQkQZ8iFsIMXbeAp5vPGbdau5dCFEgL+DfC87w==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/example-server-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDUjCCAjqgAwIBAgIUE56RLVss9rH/ojHQlVqysg6vJQUwDQYJKoZIhvcNAQEL\nBQAwVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1c3RyYWxpYTEPMA0GA1UEBwwG\nU3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYDVQQDDAZKYWVnZXIwHhcNMjIw\nOTEwMDAyMTQ0WhcNMzIwOTA3MDAyMTQ0WjBVMQswCQYDVQQGEwJBVTESMBAGA1UE\nCAwJQXVzdHJhbGlhMQ8wDQYDVQQHDAZTeWRuZXkxEDAOBgNVBAoMB0xvZ3ouaW8x\nDzANBgNVBAMMBkphZWdlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN17nlVlHzFoEDnAA7kvrjzuKiZQZ70znDW5TrqtwXqHr5XG0m7rdQlt9xyr3HFg\nDbXbkg7wBidqUySWZ7N/cxiqB/oMnfbntapwmBP77Ss8KLLQx17Geb8pryIHrhcE\na/E556epv3WRkoz3j8ph3DY7g+ghQWNtWI3UvBdaIkmPaS+wVfH6hwzpT4rbdVSF\n1n7SnMcJccKPEPgqASiEsYZeQgnZUedayKzHRnJeQD3lOPXLHAOIGHajGvyQFMqE\nfG9dJfWNVxH/+GxMNul9jsUfJMc99mG/vy3B1WROOl2EiTi8FzfM64lo8SvEs3Db\njcAFItI7BcyM/MJxqYtYFQ0CAwEAAaMaMBgwFgYDVR0RBA8wDYILZXhhbXBsZS5j\nb20wDQYJKoZIhvcNAQELBQADggEBAFjZrgLJiezjX2enrh1pJDRrj9NClTKM8Vck\ndnpI4OFmViqSyUkyY28PO9omoXUPAbcVuXcGQ/f4PR7tlKmv1lGH/4vGGgmvLjus\nMm0vYZoBos/KPN92RIUkpO1Lvt3es96CFI0k6G0JmstXn4EShQibm1424jTWU3tF\npraOAsaTVWO/ukVPbULJ8dWzKoQVTyb/cNQiPiL0IXx7XYc/cqCB2yqzELtMOmIe\nkQuyCmUNzK1qQaezxwkMl2P+121QdOvKkxcu7XlAEo0SRNNNkpOkyRqLvC2iou39\nSHxqc/Vbf+Pj9N6oC0twI7KAJELHMi9qhlQsNssxUMjYe7BRYmQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/example-server-key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA3XueVWUfMWgQOcADuS+uPO4qJlBnvTOcNblOuq3BeoevlcbS\nbut1CW33HKvccWANtduSDvAGJ2pTJJZns39zGKoH+gyd9ue1qnCYE/vtKzwostDH\nXsZ5vymvIgeuFwRr8Tnnp6m/dZGSjPePymHcNjuD6CFBY21YjdS8F1oiSY9pL7BV\n8fqHDOlPitt1VIXWftKcxwlxwo8Q+CoBKISxhl5CCdlR51rIrMdGcl5APeU49csc\nA4gYdqMa/JAUyoR8b10l9Y1XEf/4bEw26X2OxR8kxz32Yb+/LcHVZE46XYSJOLwX\nN8zriWjxK8SzcNuNwAUi0jsFzIz8wnGpi1gVDQIDAQABAoIBAQCAE55J74IMRgsr\n+hetHR966JbDNTfoN1Ib1x7p4NTDkHc++4xwzAQQAeEmWVPO1CbZhTF/JdnJLTkL\nLVamfAsItjqKpIUsZG2vNBEdbU+G8vDuBsFj0w5QN0CpQxuu/8WT51JIqGapDBdd\nIUOrWs/HJL9wmtp/LppI2j4ymtK9Cffce8AVTazfHspVF2e05b8GEeBjoMmvpPgw\nbvHPLdCVoWPGsYOFUWG9V1eCo2CFtvspsa8CYghpaXg7EOElF73W1gEoEd5SdMx9\nsvHeH4bJAzrWoqDrC5kOJUZRip9YjF8WXRudVmSaRPHptwN6qRvF8HWiGrYrdTtJ\nj1seb87BAoGBAPUrxCI64EN/6YYNziM49RpORLVrZGLaZQCf0IJkoH3DcsBcrtF8\nhqJC73z75kj1Y+oOzulYPBlhQr+4hvbMSzHwsffi5nepPXSSGK2+D1O5rASou7b3\nRe/OiJNex7IrDAy354PV4B/7iFmgGOVUn+sXIKoprqnor7f3mALAa/yZAoGBAOdE\nAMKktCQYIHweKPF0mYDsOnoJ8TEAydxShOan5r5gkVTnZhDHa3fD9eGh19Mfi9qC\ncDro5Sq1+8OLoX6Ta/Ju3PNfI2Qn4KLF9CZrEQhrV90HmXluCflZXyL71SB8pGVo\n5ybr8UtalUXVPXKi+inK7CXaJBZaboJWnqmaqJCVAoGAdIX0lgA9jldA+gGds4fi\nljoU1dTQxVrfHkjWpOKGlL9Lzrk+LTpuEriVcmWWsZ5PenLHTIgvKDDdtJlTLAE0\ny+uF6jbhKoY5OyokqI7oYfahFyXK8c7cYnla2A/4AWoMNA9D7Zi9CPZXe6Fns7dg\nui8nyzg8V2zL9zep+8TQjiECgYEAm2zTif0BaGSiqGfoomX3qHKa1lwKMiHSiHUZ\nBp9+7yGdas9dhBdSPZqAjJSlpSlFZ6RUYvMU2UCXJJOaBKR1XuhtLE8bTPuT+DFL\n5en894iU82JhHf/7Sg5rZuqTERNTtSfsefcGItuNCPLIKlwn/qB3VvUlXbSHIqeu\nWFQtx4UCgYEA1FIVEc4BjRE6jH80X7RSSOLJ6PwPglZzM8JEVyiYHAHE65zdORF1\niCiuI+pRQc3yHkm2gbB+hY5HSrCmyJrJc0tcUd4QoMqOHV8UEGLVwxtr/4DPMsl4\nJIEmzmgvs56TJeKX0YlXnD612zjDWCPV6q+LWlUUzd8qLwk6L1+EFhE=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/gen-certs.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# The following commands were used to create the CA, server and client's certificates and keys in this directory used by unit tests.\n# These certificates use the Subject Alternative Name extension rather than the Common Name, which will be unsupported in Go 1.15.\n\nusage() {\n  echo \"Usage: $0 [-d]\"\n  echo\n  echo \"-d  Dry-run mode. PEM files will not be modified.\"\n  exit 1\n}\n\ndry_run=false\n\nwhile getopts \"d\" o; do\n    case \"${o}\" in\n        d)\n            dry_run=true\n            ;;\n        *)\n            usage\n            ;;\n    esac\ndone\nshift $((OPTIND-1))\n\nset -ex\n\n# Create temp dir for generated files.\ntmp_dir=$(mktemp -d -t certificates)\nclean_up() {\n    ARG=$?\n    if [ $dry_run = true ]; then\n      echo \"Dry-run complete. Generated files can be found in $tmp_dir\"\n    else\n      rm -rf \"$tmp_dir\"\n    fi\n    exit $ARG\n}\ntrap clean_up EXIT\n\ngen_ssl_conf() {\n  domain_name=$1\n  output_file=$2\n\n  cat << EOF > \"$output_file\"\n[ req ]\nprompt              = no\ndefault_bits        = 2048\ndistinguished_name  = req_distinguished_name\nreq_extensions      = req_ext\n\n[ req_distinguished_name ]\ncountryName         = AU\nstateOrProvinceName = Australia\nlocalityName        = Sydney\norganizationName    = Logz.io\ncommonName          = Jaeger\n\n[ req_ext ]\nsubjectAltName      = @alt_names\n\n[alt_names]\nDNS.1               = $domain_name\nEOF\n}\n\n# Generate config files.\n# The server name (under alt_names in the ssl.conf) is `example.com`. (in accordance to [RFC 2006](https://tools.ietf.org/html/rfc2606))\ngen_ssl_conf example.com \"$tmp_dir/ssl.conf\"\ngen_ssl_conf wrong.com \"$tmp_dir/wrong-ssl.conf\"\n\n# Create CA (accept defaults from prompts).\nopenssl genrsa -out \"$tmp_dir/example-CA-key.pem\"  2048\nopenssl req -new -key \"$tmp_dir/example-CA-key.pem\" -x509 -days 3650 -out \"$tmp_dir/example-CA-cert.pem\" -config \"$tmp_dir/ssl.conf\"\n\n# Create Wrong CA (a dummy CA which doesn't provide any certificate; accept defaults from prompts).\nopenssl genrsa -out \"$tmp_dir/wrong-CA-key.pem\" 2048\nopenssl req -new -key \"$tmp_dir/wrong-CA-key.pem\" -x509 -days 3650 -out \"$tmp_dir/wrong-CA-cert.pem\" -config \"$tmp_dir/wrong-ssl.conf\"\n\n# Create client and server keys.\nopenssl genrsa -out \"$tmp_dir/example-server-key.pem\" 2048\nopenssl genrsa -out \"$tmp_dir/example-client-key.pem\" 2048\n\n# Create certificate sign request using the above created keys and configuration given and commandline arguments.\nopenssl req -new -nodes -key \"$tmp_dir/example-server-key.pem\" -out \"$tmp_dir/example-server.csr\" -config \"$tmp_dir/ssl.conf\"\nopenssl req -new -nodes -key \"$tmp_dir/example-client-key.pem\" -out \"$tmp_dir/example-client.csr\" -config \"$tmp_dir/ssl.conf\"\n\n# Creating the client and server certificate.\nopenssl x509 -req \\\n             -sha256 \\\n             -days 3650 \\\n             -in \"$tmp_dir/example-server.csr\" \\\n             -out \"$tmp_dir/example-server-cert.pem\" \\\n             -extensions req_ext \\\n             -CA \"$tmp_dir/example-CA-cert.pem\" \\\n             -CAkey \"$tmp_dir/example-CA-key.pem\" \\\n             -CAcreateserial \\\n             -extfile \"$tmp_dir/ssl.conf\"\nopenssl x509 -req \\\n             -sha256 \\\n             -days 3650 \\\n             -in \"$tmp_dir/example-client.csr\" \\\n             -out \"$tmp_dir/example-client-cert.pem\" \\\n             -extensions req_ext \\\n             -CA \"$tmp_dir/example-CA-cert.pem\" \\\n             -CAkey \"$tmp_dir/example-CA-key.pem\" \\\n             -CAcreateserial \\\n             -extfile \"$tmp_dir/ssl.conf\"\n\n# Copy PEM files.\nif [ $dry_run = false ]; then\n  cp \"$tmp_dir/example-CA-cert.pem\" \\\n     \"$tmp_dir/example-client-cert.pem\" \\\n     \"$tmp_dir/example-client-key.pem\" \\\n     \"$tmp_dir/example-server-cert.pem\" \\\n     \"$tmp_dir/example-server-key.pem\" \\\n     \"$tmp_dir/wrong-CA-cert.pem\" .\nfi\n"
  },
  {
    "path": "internal/config/tlscfg/testdata/wrong-CA-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDMTCCAhkCFHHHf3IkwJYPKija3g7MR53ttN+QMA0GCSqGSIb3DQEBCwUAMFUx\nCzAJBgNVBAYTAkFVMRIwEAYDVQQIDAlBdXN0cmFsaWExDzANBgNVBAcMBlN5ZG5l\neTEQMA4GA1UECgwHTG9nei5pbzEPMA0GA1UEAwwGSmFlZ2VyMB4XDTIyMDkxMDAw\nMjE0NFoXDTMyMDkwNzAwMjE0NFowVTELMAkGA1UEBhMCQVUxEjAQBgNVBAgMCUF1\nc3RyYWxpYTEPMA0GA1UEBwwGU3lkbmV5MRAwDgYDVQQKDAdMb2d6LmlvMQ8wDQYD\nVQQDDAZKYWVnZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDWRKQ\nVcJGGiQVrAreflpWMaurA+dPBhKwrfiHZ1glFEevEyOcgXVP6pSvRsUYJ1x36yGq\n8EV2bEAmpkBx0QEnPOiQIRfA8nuFmy9bE6RWl2amXoEg81E7LhW0uC9qGkF9eLG6\no9F/knd0WGNCcEpfk9NdXH1HtNJJvjNNi6no/9KHHfT2pg/3OSzt3dCPOHwXZEdL\n72rLs+NoV0sM1oP1MFZmYHzSNhquOKGWDEPTk58YvA5uRe06nacLt30ZCZh3oBso\nvJxS8Cs7mAGZMnZrMbTcNd7iYN850lTlcYFmv/3zlzF7fO84mPBzWYSY90bJ2AwQ\nAdJFrZl5sol/j9STAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALYxUQPwRg+i3yVe\n6Q4LuaJdTTJMF/ABHHffAZqAEjkfnzODimRyroGN6l5ixCRokbJ9q2Az7JLSj/Tv\ny+5O4Tu8P3Z5nFkTPE70JDEw+sFKxHCGdQ8eNf+DlL7Ado+75a8Yug2RM4wHODVh\nTKfHwj7c7vHnPkv8o8a4DiCQBNZmH6qXwCqLAYdeScrrhUzX04+3ZEq0boIYUTFn\nFYqsdpKYFwwrQ1njlUu4VDGl02QXD3HYnkbzBzEI7HW7lxfcb2py8xrdIw9bh1fl\nr+ou4bKctTJ4NOf3TezWT01MEGCgLgI8vdsyVGRUbPrfuHHVyuYARPXmEe89Q34+\nfHP/TLM=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "internal/converter/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package converter contains various utilities for converting model.Trace\n// to/from other data modes, like Thrift, or UI JSON.\npackage converter\n"
  },
  {
    "path": "internal/converter/empty_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage converter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestDummy(*testing.T) {\n\t// This is a dummy test in the root package.\n\t// Without it `go test -v .` prints \"testing: warning: no tests to run\".\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/converter/thrift/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package thrift allows converting model.Trace to/from various thrift models.\npackage thrift\n"
  },
  {
    "path": "internal/converter/thrift/empty_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage thrift\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestDummy(*testing.T) {\n\t// This is a dummy test in the root package.\n\t// Without it `go test -v .` prints \"testing: warning: no tests to run\".\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package jaeger allows converting model.Trace to/from jaeger.thrift model.\npackage jaeger\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/fixtures/domain_01.json",
    "content": "{\n  \"spans\": [\n    {\n      \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n      \"spanId\": \"AAAAAABkfZg=\",\n      \"operationName\": \"get\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n          \"spanId\": \"AAAAAABoxOM=\"\n        }\n      ],\n      \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"duration\": \"22938000ns\",\n      \"tags\": [\n        {\n          \"key\": \"http.url\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"http://127.0.0.1:15598/client_transactions\"\n        },\n        {\n          \"key\": \"span.kind\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"server\"\n        },\n        {\n          \"key\": \"peer.port\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 53931\n        },\n        {\n          \"key\": \"someBool\",\n          \"vType\": \"BOOL\",\n          \"vBool\": true\n        },\n        {\n          \"key\": \"someDouble\",\n          \"vType\": \"FLOAT64\",\n          \"vFloat64\": 129.8\n        },\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"rtapi\"\n        },\n        {\n          \"key\": \"peer.ipv4\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 3224716605\n        }\n      ],\n      \"process\": {\n        \"serviceName\": \"api\",\n        \"tags\": [\n          {\n            \"key\": \"hostname\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"api246-sjc1\"\n          },\n          {\n            \"key\": \"ip\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"10.53.69.61\"\n          },\n          {\n            \"key\": \"jaeger.version\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"Python-3.1.0\"\n          }\n        ]\n      },\n      \"logs\": [\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"key1\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value1\"\n            },\n            {\n              \"key\": \"key2\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value2\"\n            }\n          ]\n        },\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"event\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"nothing\"\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/fixtures/domain_02.json",
    "content": "{\n  \"spans\": [\n    {\n      \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n      \"spanId\": \"AAAAAABkfZg=\",\n      \"operationName\": \"get\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n          \"spanId\": \"AAAAAABoxOM=\"\n        }\n      ],\n      \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"duration\": \"22938000ns\",\n      \"tags\": [\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"BINARY\",\n          \"vBinary\": \"AAAAAAAAMDk=\"\n        }\n      ],\n      \"process\": {\n        \"serviceName\": \"api\"\n      }\n    },\n    {\n      \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n      \"spanId\": \"AAAAAABkfZk=\",\n      \"operationName\": \"get\",\n      \"references\": [\n        {\n          \"refType\": \"FOLLOWS_FROM\",\n          \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n          \"spanId\": \"AAAAAABoxOM=\"\n        }\n      ],\n      \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"duration\": \"22938000ns\",\n      \"process\": {\n        \"serviceName\": \"api\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/fixtures/domain_03.json",
    "content": "{\n  \"spans\": [\n    {\n      \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n      \"spanId\": \"AAAAAABkfZg=\",\n      \"operationName\": \"get\",\n      \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"duration\": \"22938000ns\",\n      \"tags\": [\n        {\n          \"key\": \"http.url\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"http://127.0.0.1:15598/client_transactions\"\n        },\n        {\n          \"key\": \"span.kind\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"server\"\n        },\n        {\n          \"key\": \"peer.port\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 53931\n        },\n        {\n          \"key\": \"someBool\",\n          \"vType\": \"BOOL\",\n          \"vBool\": true\n        },\n        {\n          \"key\": \"someDouble\",\n          \"vType\": \"FLOAT64\",\n          \"vFloat64\": 129.8\n        },\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"rtapi\"\n        },\n        {\n          \"key\": \"peer.ipv4\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 3224716605\n        }\n      ],\n      \"process\": {\n        \"serviceName\": \"api\",\n        \"tags\": [\n          {\n            \"key\": \"hostname\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"api246-sjc1\"\n          },\n          {\n            \"key\": \"ip\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"10.53.69.61\"\n          },\n          {\n            \"key\": \"jaeger.version\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"Python-3.1.0\"\n          }\n        ]\n      },\n      \"logs\": [\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"key1\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value1\"\n            },\n            {\n              \"key\": \"key2\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value2\"\n            }\n          ]\n        },\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"event\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"nothing\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n      \"spanId\": \"AAAAAABSlHs=\",\n      \"operationName\": \"get\",\n      \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"duration\": \"22938000ns\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAABSlpqJVVcaPw==\",\n          \"spanId\": \"AAAAAABSlHs=\"\n        }\n      ],\n      \"tags\": [\n        {\n          \"key\": \"http.url\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"http://127.0.0.1:15598/client_transactions\"\n        },\n        {\n          \"key\": \"span.kind\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"server\"\n        },\n        {\n          \"key\": \"peer.port\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 53931\n        },\n        {\n          \"key\": \"someBool\",\n          \"vType\": \"BOOL\",\n          \"vBool\": true\n        },\n        {\n          \"key\": \"someDouble\",\n          \"vType\": \"FLOAT64\",\n          \"vFloat64\": 4638770948061370000\n        },\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"rtapi\"\n        },\n        {\n          \"key\": \"peer.ipv4\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 3224716605\n        },\n        {\n          \"key\": \"some.binary.data\",\n          \"vType\": \"BINARY\",\n          \"vBinary\": \"c29tZS1iaW5hcnktZGF0YQ==\"\n        }\n      ],\n      \"process\": {\n        \"serviceName\": \"api\",\n        \"tags\": [\n          {\n            \"key\": \"hostname\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"api246-sjc1\"\n          },\n          {\n            \"key\": \"ip\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"10.53.69.61\"\n          },\n          {\n            \"key\": \"jaeger.version\",\n            \"vType\": \"STRING\",\n            \"vStr\": \"Python-3.1.0\"\n          }\n        ]\n      },\n      \"logs\": [\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"key1\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value1\"\n            },\n            {\n              \"key\": \"key2\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value2\"\n            }\n          ]\n        },\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"event\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"nothing\"\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/fixtures/thrift_batch_01.json",
    "content": "{\n  \"process\": {\n    \"serviceName\": \"api\",\n    \"tags\": [\n      {\n        \"key\": \"hostname\",\n        \"vType\": \"STRING\",\n        \"vStr\": \"api246-sjc1\"\n      },\n      {\n        \"key\": \"ip\",\n        \"vType\": \"STRING\",\n        \"vStr\": \"10.53.69.61\"\n      },\n      {\n        \"key\": \"jaeger.version\",\n        \"vType\": \"STRING\",\n        \"vStr\": \"Python-3.1.0\"\n      }\n    ]\n  },\n  \"spans\": [\n    {\n      \"traceIdLow\": 5951113872249657919,\n      \"spanId\": 6585752,\n      \"parentSpanId\": 6866147,\n      \"operationName\": \"get\",\n      \"startTime\": 1485467191639875,\n      \"duration\": 22938,\n      \"tags\": [\n        {\n          \"key\": \"http.url\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"http://127.0.0.1:15598/client_transactions\"\n        },\n        {\n          \"key\": \"span.kind\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"server\"\n        },\n        {\n          \"key\": \"peer.port\",\n          \"vType\": \"LONG\",\n          \"vLong\": 53931\n        },\n        {\n          \"key\": \"someBool\",\n          \"vType\": \"BOOL\",\n          \"vBool\": true\n        },\n        {\n          \"key\": \"someDouble\",\n          \"vType\": \"DOUBLE\",\n          \"vDouble\": 129.8\n        },\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"rtapi\"\n        },\n        {\n          \"key\": \"peer.ipv4\",\n          \"vType\": \"LONG\",\n          \"vLong\": 3224716605\n        }\n      ],\n      \"logs\": [\n        {\n          \"timestamp\": 1485467191639875,\n          \"fields\": [\n            {\n              \"key\": \"key1\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value1\"\n            },\n            {\n              \"key\": \"key2\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"value2\"\n            }\n          ]\n        },\n        {\n          \"timestamp\": 1485467191639875,\n          \"fields\": [\n            {\n              \"key\": \"event\",\n              \"vType\": \"STRING\",\n              \"vStr\": \"nothing\"\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/fixtures/thrift_batch_02.json",
    "content": "{\n  \"process\": {\n    \"serviceName\": \"api\",\n    \"tags\": []\n  },\n  \"spans\": [\n    {\n      \"traceIdLow\": 5951113872249657919,\n      \"spanId\": 6585752,\n      \"parentSpanId\": 6866147,\n      \"operationName\": \"get\",\n      \"startTime\": 1485467191639875,\n      \"duration\": 22938,\n      \"tags\": [\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"BINARY\",\n          \"vBinary\": \"AAAAAAAAMDk=\"\n        }\n      ]\n    },\n    {\n        \"traceIdLow\": 5951113872249657919,\n        \"spanId\": 6585753,\n        \"parentSpanId\": 6866147,\n        \"operationName\": \"get\",\n        \"references\": [\n            {\n                \"refType\": \"FOLLOWS_FROM\",\n                \"traceIdLow\": 5951113872249657919,\n                \"spanId\": 6866147\n            }\n        ],\n        \"startTime\": 1485467191639875,\n        \"duration\": 22938\n      }\n    ]\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/sampling_from_domain.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"errors\"\n\t\"math\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger-idl/thrift-gen/sampling\"\n)\n\n// ConvertSamplingResponseFromDomain converts proto sampling response to its thrift representation.\nfunc ConvertSamplingResponseFromDomain(r *api_v2.SamplingStrategyResponse) (*sampling.SamplingStrategyResponse, error) {\n\ttyp, err := convertStrategyTypeFromDomain(r.GetStrategyType())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trl, err := convertRateLimitingFromDomain(r.GetRateLimitingSampling())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tthriftResp := &sampling.SamplingStrategyResponse{\n\t\tStrategyType:          typ,\n\t\tProbabilisticSampling: convertProbabilisticFromDomain(r.GetProbabilisticSampling()),\n\t\tRateLimitingSampling:  rl,\n\t\tOperationSampling:     convertPerOperationFromDomain(r.GetOperationSampling()),\n\t}\n\treturn thriftResp, nil\n}\n\nfunc convertProbabilisticFromDomain(s *api_v2.ProbabilisticSamplingStrategy) *sampling.ProbabilisticSamplingStrategy {\n\tif s == nil {\n\t\treturn nil\n\t}\n\treturn &sampling.ProbabilisticSamplingStrategy{SamplingRate: s.GetSamplingRate()}\n}\n\nfunc convertRateLimitingFromDomain(s *api_v2.RateLimitingSamplingStrategy) (*sampling.RateLimitingSamplingStrategy, error) {\n\tif s == nil {\n\t\treturn nil, nil\n\t}\n\tif s.MaxTracesPerSecond > math.MaxInt16 {\n\t\treturn nil, errors.New(\"maxTracesPerSecond is higher than int16\")\n\t}\n\treturn &sampling.RateLimitingSamplingStrategy{\n\t\t//nolint:gosec // G115\n\t\tMaxTracesPerSecond: int16(s.GetMaxTracesPerSecond()),\n\t}, nil\n}\n\nfunc convertPerOperationFromDomain(s *api_v2.PerOperationSamplingStrategies) *sampling.PerOperationSamplingStrategies {\n\tif s == nil {\n\t\treturn nil\n\t}\n\tr := &sampling.PerOperationSamplingStrategies{\n\t\tDefaultSamplingProbability:       s.GetDefaultSamplingProbability(),\n\t\tDefaultLowerBoundTracesPerSecond: s.GetDefaultLowerBoundTracesPerSecond(),\n\t\tDefaultUpperBoundTracesPerSecond: &s.DefaultUpperBoundTracesPerSecond,\n\t}\n\n\tperOp := s.GetPerOperationStrategies()\n\t// Default to empty array so that json.Marshal returns [] instead of null (Issue #3891).\n\tr.PerOperationStrategies = make([]*sampling.OperationSamplingStrategy, len(perOp))\n\tfor i, k := range perOp {\n\t\tr.PerOperationStrategies[i] = convertOperationFromDomain(k)\n\t}\n\treturn r\n}\n\nfunc convertOperationFromDomain(s *api_v2.OperationSamplingStrategy) *sampling.OperationSamplingStrategy {\n\tif s == nil {\n\t\treturn nil\n\t}\n\treturn &sampling.OperationSamplingStrategy{\n\t\tOperation:             s.GetOperation(),\n\t\tProbabilisticSampling: convertProbabilisticFromDomain(s.GetProbabilisticSampling()),\n\t}\n}\n\nfunc convertStrategyTypeFromDomain(s api_v2.SamplingStrategyType) (sampling.SamplingStrategyType, error) {\n\tswitch s {\n\tcase api_v2.SamplingStrategyType_PROBABILISTIC:\n\t\treturn sampling.SamplingStrategyType_PROBABILISTIC, nil\n\tcase api_v2.SamplingStrategyType_RATE_LIMITING:\n\t\treturn sampling.SamplingStrategyType_RATE_LIMITING, nil\n\tdefault:\n\t\treturn sampling.SamplingStrategyType_PROBABILISTIC, errors.New(\"could not convert sampling strategy type\")\n\t}\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/sampling_from_domain_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger-idl/thrift-gen/sampling\"\n)\n\nfunc TestConvertStrategyTypeFromDomain(t *testing.T) {\n\ttests := []struct {\n\t\texpected sampling.SamplingStrategyType\n\t\tin       api_v2.SamplingStrategyType\n\t\terr      string\n\t}{\n\t\t{expected: sampling.SamplingStrategyType_PROBABILISTIC, in: api_v2.SamplingStrategyType_PROBABILISTIC},\n\t\t{expected: sampling.SamplingStrategyType_RATE_LIMITING, in: api_v2.SamplingStrategyType_RATE_LIMITING},\n\t\t{in: 44, err: \"could not convert sampling strategy type\"},\n\t}\n\tfor _, test := range tests {\n\t\tst, err := convertStrategyTypeFromDomain(test.in)\n\t\tif test.err != \"\" {\n\t\t\trequire.EqualError(t, err, test.err)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expected, st)\n\t\t}\n\t}\n}\n\nfunc TestConvertProbabilisticFromDomain(t *testing.T) {\n\ttests := []struct {\n\t\tin       *api_v2.ProbabilisticSamplingStrategy\n\t\texpected *sampling.ProbabilisticSamplingStrategy\n\t}{\n\t\t{in: &api_v2.ProbabilisticSamplingStrategy{SamplingRate: 21}, expected: &sampling.ProbabilisticSamplingStrategy{SamplingRate: 21}},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\tst := convertProbabilisticFromDomain(test.in)\n\t\tassert.Equal(t, test.expected, st)\n\t}\n}\n\nfunc TestConvertRateLimitingFromDomain(t *testing.T) {\n\ttests := []struct {\n\t\tin       *api_v2.RateLimitingSamplingStrategy\n\t\texpected *sampling.RateLimitingSamplingStrategy\n\t\terr      string\n\t}{\n\t\t{in: &api_v2.RateLimitingSamplingStrategy{MaxTracesPerSecond: 21}, expected: &sampling.RateLimitingSamplingStrategy{MaxTracesPerSecond: 21}},\n\t\t{in: &api_v2.RateLimitingSamplingStrategy{MaxTracesPerSecond: math.MaxInt32}, err: \"maxTracesPerSecond is higher than int16\"},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\tst, err := convertRateLimitingFromDomain(test.in)\n\t\tif test.err != \"\" {\n\t\t\trequire.EqualError(t, err, test.err)\n\t\t\trequire.Nil(t, st)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expected, st)\n\t\t}\n\t}\n}\n\nfunc TestConvertOperationStrategyFromDomain(t *testing.T) {\n\ttests := []struct {\n\t\tin       *api_v2.OperationSamplingStrategy\n\t\texpected *sampling.OperationSamplingStrategy\n\t}{\n\t\t{in: &api_v2.OperationSamplingStrategy{Operation: \"foo\"}, expected: &sampling.OperationSamplingStrategy{Operation: \"foo\"}},\n\t\t{\n\t\t\tin:       &api_v2.OperationSamplingStrategy{Operation: \"foo\", ProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{SamplingRate: 2}},\n\t\t\texpected: &sampling.OperationSamplingStrategy{Operation: \"foo\", ProbabilisticSampling: &sampling.ProbabilisticSamplingStrategy{SamplingRate: 2}},\n\t\t},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\to := convertOperationFromDomain(test.in)\n\t\tassert.Equal(t, test.expected, o)\n\t}\n}\n\nfunc TestConvertPerOperationStrategyFromDomain(t *testing.T) {\n\ta := 11.2\n\ttests := []struct {\n\t\tin       *api_v2.PerOperationSamplingStrategies\n\t\texpected *sampling.PerOperationSamplingStrategies\n\t}{\n\t\t{\n\t\t\tin: &api_v2.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability: 15.2, DefaultUpperBoundTracesPerSecond: a, DefaultLowerBoundTracesPerSecond: 2,\n\t\t\t\tPerOperationStrategies: []*api_v2.OperationSamplingStrategy{{Operation: \"fao\"}},\n\t\t\t},\n\t\t\texpected: &sampling.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability: 15.2, DefaultUpperBoundTracesPerSecond: &a, DefaultLowerBoundTracesPerSecond: 2,\n\t\t\t\tPerOperationStrategies: []*sampling.OperationSamplingStrategy{{Operation: \"fao\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tin: &api_v2.PerOperationSamplingStrategies{DefaultSamplingProbability: 15.2, DefaultUpperBoundTracesPerSecond: a, DefaultLowerBoundTracesPerSecond: 2},\n\t\t\texpected: &sampling.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability: 15.2, DefaultUpperBoundTracesPerSecond: &a, DefaultLowerBoundTracesPerSecond: 2,\n\t\t\t\tPerOperationStrategies: []*sampling.OperationSamplingStrategy{},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\to := convertPerOperationFromDomain(test.in)\n\t\tassert.Equal(t, test.expected, o)\n\t}\n}\n\nfunc TestConvertSamplingResponseFromDomain(t *testing.T) {\n\ttests := []struct {\n\t\tin       *api_v2.SamplingStrategyResponse\n\t\texpected *sampling.SamplingStrategyResponse\n\t\terr      string\n\t}{\n\t\t{in: &api_v2.SamplingStrategyResponse{StrategyType: 55}, err: \"could not convert sampling strategy type\"},\n\t\t{\n\t\t\tin:  &api_v2.SamplingStrategyResponse{StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC, RateLimitingSampling: &api_v2.RateLimitingSamplingStrategy{MaxTracesPerSecond: math.MaxInt32}},\n\t\t\terr: \"maxTracesPerSecond is higher than int16\",\n\t\t},\n\t\t{in: &api_v2.SamplingStrategyResponse{StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC}, expected: &sampling.SamplingStrategyResponse{StrategyType: sampling.SamplingStrategyType_PROBABILISTIC}},\n\t}\n\tfor _, test := range tests {\n\t\tr, err := ConvertSamplingResponseFromDomain(test.in)\n\t\tif test.err != \"\" {\n\t\t\trequire.EqualError(t, err, test.err)\n\t\t\trequire.Nil(t, r)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expected, r)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/sampling_to_domain.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"errors\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger-idl/thrift-gen/sampling\"\n)\n\n// ConvertSamplingResponseToDomain converts thrift sampling response to its proto representation.\nfunc ConvertSamplingResponseToDomain(r *sampling.SamplingStrategyResponse) (*api_v2.SamplingStrategyResponse, error) {\n\tif r == nil {\n\t\treturn nil, nil\n\t}\n\tt, err := convertStrategyTypeToDomain(r.GetStrategyType())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresponse := &api_v2.SamplingStrategyResponse{\n\t\tStrategyType:          t,\n\t\tProbabilisticSampling: convertProbabilisticToDomain(r.GetProbabilisticSampling()),\n\t\tRateLimitingSampling:  convertRateLimitingToDomain(r.GetRateLimitingSampling()),\n\t\tOperationSampling:     convertPerOperationToDomain(r.GetOperationSampling()),\n\t}\n\treturn response, nil\n}\n\nfunc convertRateLimitingToDomain(s *sampling.RateLimitingSamplingStrategy) *api_v2.RateLimitingSamplingStrategy {\n\tif s == nil {\n\t\treturn nil\n\t}\n\treturn &api_v2.RateLimitingSamplingStrategy{MaxTracesPerSecond: int32(s.GetMaxTracesPerSecond())}\n}\n\nfunc convertProbabilisticToDomain(s *sampling.ProbabilisticSamplingStrategy) *api_v2.ProbabilisticSamplingStrategy {\n\tif s == nil {\n\t\treturn nil\n\t}\n\treturn &api_v2.ProbabilisticSamplingStrategy{SamplingRate: s.GetSamplingRate()}\n}\n\nfunc convertPerOperationToDomain(s *sampling.PerOperationSamplingStrategies) *api_v2.PerOperationSamplingStrategies {\n\tif s == nil {\n\t\treturn nil\n\t}\n\tposs := make([]*api_v2.OperationSamplingStrategy, len(s.PerOperationStrategies))\n\tfor i, pos := range s.PerOperationStrategies {\n\t\tposs[i] = &api_v2.OperationSamplingStrategy{\n\t\t\tOperation:             pos.Operation,\n\t\t\tProbabilisticSampling: convertProbabilisticToDomain(pos.GetProbabilisticSampling()),\n\t\t}\n\t}\n\treturn &api_v2.PerOperationSamplingStrategies{\n\t\tDefaultSamplingProbability:       s.GetDefaultSamplingProbability(),\n\t\tDefaultUpperBoundTracesPerSecond: s.GetDefaultUpperBoundTracesPerSecond(),\n\t\tDefaultLowerBoundTracesPerSecond: s.GetDefaultLowerBoundTracesPerSecond(),\n\t\tPerOperationStrategies:           poss,\n\t}\n}\n\nfunc convertStrategyTypeToDomain(t sampling.SamplingStrategyType) (api_v2.SamplingStrategyType, error) {\n\tswitch t {\n\tcase sampling.SamplingStrategyType_PROBABILISTIC:\n\t\treturn api_v2.SamplingStrategyType_PROBABILISTIC, nil\n\tcase sampling.SamplingStrategyType_RATE_LIMITING:\n\t\treturn api_v2.SamplingStrategyType_RATE_LIMITING, nil\n\tdefault:\n\t\treturn api_v2.SamplingStrategyType_PROBABILISTIC, errors.New(\"could not convert sampling strategy type\")\n\t}\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/sampling_to_domain_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger-idl/thrift-gen/sampling\"\n)\n\nfunc TestConvertStrategyTypeToDomain(t *testing.T) {\n\ttests := []struct {\n\t\tin       sampling.SamplingStrategyType\n\t\texpected api_v2.SamplingStrategyType\n\t\terr      error\n\t}{\n\t\t{in: sampling.SamplingStrategyType_PROBABILISTIC, expected: api_v2.SamplingStrategyType_PROBABILISTIC},\n\t\t{in: sampling.SamplingStrategyType_RATE_LIMITING, expected: api_v2.SamplingStrategyType_RATE_LIMITING},\n\t\t{in: 44, err: errors.New(\"could not convert sampling strategy type\")},\n\t}\n\tfor _, test := range tests {\n\t\tst, err := convertStrategyTypeToDomain(test.in)\n\t\tif test.err != nil {\n\t\t\trequire.EqualError(t, test.err, err.Error())\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expected, st)\n\t\t}\n\t}\n}\n\nfunc TestConvertProbabilisticToDomain(t *testing.T) {\n\ttests := []struct {\n\t\texpected *api_v2.ProbabilisticSamplingStrategy\n\t\tin       *sampling.ProbabilisticSamplingStrategy\n\t}{\n\t\t{expected: &api_v2.ProbabilisticSamplingStrategy{SamplingRate: 21}, in: &sampling.ProbabilisticSamplingStrategy{SamplingRate: 21}},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\tst := convertProbabilisticToDomain(test.in)\n\t\tassert.Equal(t, test.expected, st)\n\t}\n}\n\nfunc TestConvertRateLimitingToDomain(t *testing.T) {\n\ttests := []struct {\n\t\texpected *api_v2.RateLimitingSamplingStrategy\n\t\tin       *sampling.RateLimitingSamplingStrategy\n\t}{\n\t\t{expected: &api_v2.RateLimitingSamplingStrategy{MaxTracesPerSecond: 21}, in: &sampling.RateLimitingSamplingStrategy{MaxTracesPerSecond: 21}},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\tst := convertRateLimitingToDomain(test.in)\n\t\tassert.Equal(t, test.expected, st)\n\t}\n}\n\nfunc TestConvertPerOperationStrategyToDomain(t *testing.T) {\n\ta := 11.2\n\ttests := []struct {\n\t\texpected *api_v2.PerOperationSamplingStrategies\n\t\tin       *sampling.PerOperationSamplingStrategies\n\t}{\n\t\t{\n\t\t\texpected: &api_v2.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability: 15.2, DefaultUpperBoundTracesPerSecond: a, DefaultLowerBoundTracesPerSecond: 2,\n\t\t\t\tPerOperationStrategies: []*api_v2.OperationSamplingStrategy{{Operation: \"fao\"}},\n\t\t\t},\n\t\t\tin: &sampling.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability: 15.2, DefaultUpperBoundTracesPerSecond: &a, DefaultLowerBoundTracesPerSecond: 2,\n\t\t\t\tPerOperationStrategies: []*sampling.OperationSamplingStrategy{{Operation: \"fao\"}},\n\t\t\t},\n\t\t},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\to := convertPerOperationToDomain(test.in)\n\t\tassert.Equal(t, test.expected, o)\n\t}\n}\n\nfunc TestConvertSamplingResponseToDomain(t *testing.T) {\n\ttests := []struct {\n\t\texpected *api_v2.SamplingStrategyResponse\n\t\tin       *sampling.SamplingStrategyResponse\n\t\terr      string\n\t}{\n\t\t{in: &sampling.SamplingStrategyResponse{StrategyType: 55}, err: \"could not convert sampling strategy type\"},\n\t\t{expected: &api_v2.SamplingStrategyResponse{StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC}, in: &sampling.SamplingStrategyResponse{StrategyType: sampling.SamplingStrategyType_PROBABILISTIC}},\n\t\t{},\n\t}\n\tfor _, test := range tests {\n\t\tr, err := ConvertSamplingResponseToDomain(test.in)\n\t\tif test.err != \"\" {\n\t\t\trequire.EqualError(t, err, test.err)\n\t\t\trequire.Nil(t, r)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expected, r)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/to_domain.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger\"\n)\n\n// ToDomain transforms a set of spans and a process in jaeger.thrift format into a slice of model.Span.\n// A valid []*model.Span is always returned, even when there are errors.\n// Errors are presented as tags on spans\nfunc ToDomain(jSpans []*jaeger.Span, jProcess *jaeger.Process) []*model.Span {\n\treturn toDomain{}.ToDomain(jSpans, jProcess)\n}\n\n// ToDomainSpan transforms a span in jaeger.thrift format into model.Span.\n// A valid model.Span is always returned, even when there are errors.\n// Errors are presented as tags on spans\nfunc ToDomainSpan(jSpan *jaeger.Span, jProcess *jaeger.Process) *model.Span {\n\treturn toDomain{}.ToDomainSpan(jSpan, jProcess)\n}\n\n// ToDomainProcess transforms a process in jaeger.thrift format to model.Span.\nfunc ToDomainProcess(jProcess *jaeger.Process) *model.Process {\n\treturn toDomain{}.getProcess(jProcess)\n}\n\ntype toDomain struct{}\n\nfunc (td toDomain) ToDomain(jSpans []*jaeger.Span, jProcess *jaeger.Process) []*model.Span {\n\tspans := make([]*model.Span, len(jSpans))\n\tmProcess := td.getProcess(jProcess)\n\tfor i, jSpan := range jSpans {\n\t\tspans[i] = td.transformSpan(jSpan, mProcess)\n\t}\n\treturn spans\n}\n\nfunc (td toDomain) ToDomainSpan(jSpan *jaeger.Span, jProcess *jaeger.Process) *model.Span {\n\tmProcess := td.getProcess(jProcess)\n\treturn td.transformSpan(jSpan, mProcess)\n}\n\nfunc (td toDomain) transformSpan(jSpan *jaeger.Span, mProcess *model.Process) *model.Span {\n\t//nolint:gosec // G115\n\ttraceID := model.NewTraceID(uint64(jSpan.TraceIdHigh), uint64(jSpan.TraceIdLow))\n\t// allocate extra space for future append operation\n\ttags := td.getTags(jSpan.Tags, 1)\n\trefs := td.getReferences(jSpan.References)\n\t// We no longer store ParentSpanID in the domain model, but the data in Thrift model\n\t// might still have these IDs without representing them in the References, so we\n\t// convert it back into child-of reference.\n\tif jSpan.ParentSpanId != 0 {\n\t\t//nolint:gosec // G115\n\t\tparentSpanID := model.NewSpanID(uint64(jSpan.ParentSpanId))\n\t\trefs = model.MaybeAddParentSpanID(traceID, parentSpanID, refs)\n\t}\n\treturn &model.Span{\n\t\tTraceID: traceID,\n\t\t//nolint:gosec // G115\n\t\tSpanID:        model.NewSpanID(uint64(jSpan.SpanId)),\n\t\tOperationName: jSpan.OperationName,\n\t\tReferences:    refs,\n\t\t//nolint:gosec // G115\n\t\tFlags: model.Flags(jSpan.Flags),\n\t\t//nolint:gosec // G115\n\t\tStartTime: model.EpochMicrosecondsAsTime(uint64(jSpan.StartTime)),\n\t\t//nolint:gosec // G115\n\t\tDuration: model.MicrosecondsAsDuration(uint64(jSpan.Duration)),\n\t\tTags:     tags,\n\t\tLogs:     td.getLogs(jSpan.Logs),\n\t\tProcess:  mProcess,\n\t}\n}\n\nfunc (toDomain) getReferences(jRefs []*jaeger.SpanRef) []model.SpanRef {\n\tif len(jRefs) == 0 {\n\t\treturn nil\n\t}\n\n\tmRefs := make([]model.SpanRef, len(jRefs))\n\tfor idx, jRef := range jRefs {\n\t\tmRefs[idx] = model.SpanRef{\n\t\t\t//nolint:gosec // G115\n\t\t\tRefType: model.SpanRefType(int(jRef.RefType)),\n\t\t\t//nolint:gosec // G115\n\t\t\tTraceID: model.NewTraceID(uint64(jRef.TraceIdHigh), uint64(jRef.TraceIdLow)),\n\t\t\t//nolint:gosec // G115\n\t\t\tSpanID: model.NewSpanID(uint64(jRef.SpanId)),\n\t\t}\n\t}\n\n\treturn mRefs\n}\n\n// getProcess takes a jaeger.thrift process and produces a model.Process.\n// Any errors are presented as tags\nfunc (td toDomain) getProcess(jProcess *jaeger.Process) *model.Process {\n\tif jProcess == nil {\n\t\treturn nil\n\t}\n\ttags := td.getTags(jProcess.Tags, 0)\n\treturn &model.Process{\n\t\tTags:        tags,\n\t\tServiceName: jProcess.ServiceName,\n\t}\n}\n\n// convert the jaeger.Tag slice to domain KeyValue slice\n// zipkin/to_domain.go does not give a default slice size since it has to filter annotations, jaeger conversion is more predictable\n// thus to avoid future full array copy when using append, pre-allocate extra space as an optimization\nfunc (td toDomain) getTags(tags []*jaeger.Tag, extraSpace int) model.KeyValues {\n\tif len(tags) == 0 {\n\t\treturn nil\n\t}\n\tretMe := make(model.KeyValues, len(tags), len(tags)+extraSpace)\n\tfor i, tag := range tags {\n\t\tretMe[i] = td.getTag(tag)\n\t}\n\treturn retMe\n}\n\nfunc (toDomain) getTag(tag *jaeger.Tag) model.KeyValue {\n\tswitch tag.VType {\n\tcase jaeger.TagType_BOOL:\n\t\treturn model.Bool(tag.Key, tag.GetVBool())\n\tcase jaeger.TagType_BINARY:\n\t\treturn model.Binary(tag.Key, tag.GetVBinary())\n\tcase jaeger.TagType_DOUBLE:\n\t\treturn model.Float64(tag.Key, tag.GetVDouble())\n\tcase jaeger.TagType_LONG:\n\t\treturn model.Int64(tag.Key, tag.GetVLong())\n\tcase jaeger.TagType_STRING:\n\t\treturn model.String(tag.Key, tag.GetVStr())\n\tdefault:\n\t\treturn model.String(tag.Key, fmt.Sprintf(\"Unknown VType: %+v\", tag))\n\t}\n}\n\nfunc (td toDomain) getLogs(logs []*jaeger.Log) []model.Log {\n\tif len(logs) == 0 {\n\t\treturn nil\n\t}\n\tretMe := make([]model.Log, len(logs))\n\tfor i, log := range logs {\n\t\tretMe[i] = model.Log{\n\t\t\t//nolint:gosec // G115\n\t\t\tTimestamp: model.EpochMicrosecondsAsTime(uint64(log.Timestamp)),\n\t\t\tFields:    td.getTags(log.Fields, 0),\n\t\t}\n\t}\n\treturn retMe\n}\n"
  },
  {
    "path": "internal/converter/thrift/jaeger/to_domain_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaeger\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/gogo/protobuf/proto\"\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger-idl/thrift-gen/jaeger\"\n)\n\nconst NumberOfFixtures = 2\n\nfunc TestToDomain(t *testing.T) {\n\tfor i := 1; i <= NumberOfFixtures; i++ {\n\t\tin := fmt.Sprintf(\"fixtures/thrift_batch_%02d.json\", i)\n\t\tout := fmt.Sprintf(\"fixtures/domain_%02d.json\", i)\n\t\tmSpans := loadSpans(t, out)\n\t\tfor _, s := range mSpans {\n\t\t\ts.NormalizeTimestamps()\n\t\t}\n\n\t\tjBatch := loadBatch(t, in)\n\t\tname := in + \" -> \" + out + \" : \" + jBatch.Process.ServiceName\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tactualSpans := ToDomain(jBatch.Spans, jBatch.Process)\n\t\t\tfor _, s := range actualSpans {\n\t\t\t\ts.NormalizeTimestamps()\n\t\t\t}\n\t\t\tif !assert.Equal(t, mSpans, actualSpans) {\n\t\t\t\tfor _, err := range pretty.Diff(mSpans, actualSpans) {\n\t\t\t\t\tt.Log(err)\n\t\t\t\t}\n\t\t\t\tout, err := json.Marshal(actualSpans)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tt.Logf(\"Actual trace %v: %s\", i, string(out))\n\t\t\t}\n\t\t})\n\t\tif i == 1 {\n\t\t\tt.Run(\"ToDomainSpan\", func(t *testing.T) {\n\t\t\t\tjSpan := jBatch.Spans[0]\n\t\t\t\tmSpan := ToDomainSpan(jSpan, jBatch.Process)\n\t\t\t\tmSpan.NormalizeTimestamps()\n\t\t\t\tassert.Equal(t, mSpans[0], mSpan)\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc loadSpans(t *testing.T, file string) []*model.Span {\n\tvar trace model.Trace\n\tloadJSONPB(t, file, &trace)\n\treturn trace.Spans\n}\n\nfunc loadJSONPB(t *testing.T, fileName string, obj proto.Message) {\n\tjsonFile, err := os.Open(fileName)\n\trequire.NoError(t, err, \"Failed to open json fixture file %s\", fileName)\n\trequire.NoError(t, jsonpb.Unmarshal(jsonFile, obj), fileName)\n}\n\nfunc loadBatch(t *testing.T, file string) *jaeger.Batch {\n\tvar batch jaeger.Batch\n\tloadJSON(t, file, &batch)\n\treturn &batch\n}\n\nfunc loadJSON(t *testing.T, fileName string, obj any) {\n\tjsonFile, err := os.Open(fileName)\n\trequire.NoError(t, err, \"Failed to load json fixture file %s\", fileName)\n\tjsonParser := json.NewDecoder(jsonFile)\n\terr = jsonParser.Decode(obj)\n\trequire.NoError(t, err, \"Failed to parse json fixture file %s\", fileName)\n}\n\nfunc TestUnknownJaegerType(t *testing.T) {\n\tmkv := toDomain{}.getTag(&jaeger.Tag{\n\t\tVType: 999,\n\t\tKey:   \"sneh\",\n\t})\n\texpected := model.String(\"sneh\", \"Unknown VType: Tag({Key:sneh VType:<UNSET> VStr:<nil> VDouble:<nil> VBool:<nil> VLong:<nil> VBinary:[]})\")\n\tassert.Equal(t, expected, mkv)\n}\n\nfunc TestToDomain_ToDomainProcess(t *testing.T) {\n\tp := ToDomainProcess(&jaeger.Process{ServiceName: \"foo\", Tags: []*jaeger.Tag{{Key: \"foo\", VType: jaeger.TagType_BOOL}}})\n\tassert.Equal(t, &model.Process{ServiceName: \"foo\", Tags: []model.KeyValue{{Key: \"foo\", VType: model.BoolType}}}, p)\n}\n\nfunc TestToDomain_ToDomainSpanProcessNull(t *testing.T) {\n\ttm := time.Unix(158, 0)\n\ts := ToDomainSpan(&jaeger.Span{OperationName: \"foo\", StartTime: int64(model.TimeAsEpochMicroseconds(tm))}, nil)\n\tassert.Equal(t, &model.Span{OperationName: \"foo\", StartTime: tm.UTC()}, s)\n}\n"
  },
  {
    "path": "internal/distributedlock/empty_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage distributedlock\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/distributedlock/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage distributedlock\n\nimport (\n\t\"time\"\n)\n\n// Lock uses distributed lock for control of a resource.\ntype Lock interface {\n\t// Acquire acquires a lease of duration ttl around a given resource. In case of an error,\n\t// acquired is meaningless.\n\tAcquire(resource string, ttl time.Duration) (acquired bool, err error)\n\n\t// Forfeit forfeits a lease around a given resource. In case of an error,\n\t// forfeited is meaningless.\n\tForfeit(resource string) (forfeited bool, err error)\n}\n"
  },
  {
    "path": "internal/distributedlock/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"time\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewLock creates a new instance of Lock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewLock(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Lock {\n\tmock := &Lock{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Lock is an autogenerated mock type for the Lock type\ntype Lock struct {\n\tmock.Mock\n}\n\ntype Lock_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Lock) EXPECT() *Lock_Expecter {\n\treturn &Lock_Expecter{mock: &_m.Mock}\n}\n\n// Acquire provides a mock function for the type Lock\nfunc (_mock *Lock) Acquire(resource string, ttl time.Duration) (bool, error) {\n\tret := _mock.Called(resource, ttl)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Acquire\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string, time.Duration) (bool, error)); ok {\n\t\treturn returnFunc(resource, ttl)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string, time.Duration) bool); ok {\n\t\tr0 = returnFunc(resource, ttl)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string, time.Duration) error); ok {\n\t\tr1 = returnFunc(resource, ttl)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Lock_Acquire_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Acquire'\ntype Lock_Acquire_Call struct {\n\t*mock.Call\n}\n\n// Acquire is a helper method to define mock.On call\n//   - resource string\n//   - ttl time.Duration\nfunc (_e *Lock_Expecter) Acquire(resource interface{}, ttl interface{}) *Lock_Acquire_Call {\n\treturn &Lock_Acquire_Call{Call: _e.mock.On(\"Acquire\", resource, ttl)}\n}\n\nfunc (_c *Lock_Acquire_Call) Run(run func(resource string, ttl time.Duration)) *Lock_Acquire_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\tvar arg1 time.Duration\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(time.Duration)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Lock_Acquire_Call) Return(acquired bool, err error) *Lock_Acquire_Call {\n\t_c.Call.Return(acquired, err)\n\treturn _c\n}\n\nfunc (_c *Lock_Acquire_Call) RunAndReturn(run func(resource string, ttl time.Duration) (bool, error)) *Lock_Acquire_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Forfeit provides a mock function for the type Lock\nfunc (_mock *Lock) Forfeit(resource string) (bool, error) {\n\tret := _mock.Called(resource)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Forfeit\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string) (bool, error)); ok {\n\t\treturn returnFunc(resource)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string) bool); ok {\n\t\tr0 = returnFunc(resource)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string) error); ok {\n\t\tr1 = returnFunc(resource)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Lock_Forfeit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Forfeit'\ntype Lock_Forfeit_Call struct {\n\t*mock.Call\n}\n\n// Forfeit is a helper method to define mock.On call\n//   - resource string\nfunc (_e *Lock_Expecter) Forfeit(resource interface{}) *Lock_Forfeit_Call {\n\treturn &Lock_Forfeit_Call{Call: _e.mock.On(\"Forfeit\", resource)}\n}\n\nfunc (_c *Lock_Forfeit_Call) Run(run func(resource string)) *Lock_Forfeit_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Lock_Forfeit_Call) Return(forfeited bool, err error) *Lock_Forfeit_Call {\n\t_c.Call.Return(forfeited, err)\n\treturn _c\n}\n\nfunc (_c *Lock_Forfeit_Call) RunAndReturn(run func(resource string) (bool, error)) *Lock_Forfeit_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/fswatcher/fswatcher.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage fswatcher\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"go.uber.org/zap\"\n)\n\ntype FSWatcher struct {\n\twatcher            *fsnotify.Watcher\n\tlogger             *zap.Logger\n\tfileHashContentMap map[string]string\n\tonChange           func()\n\tmu                 sync.RWMutex\n}\n\n// FSWatcher waits for notifications of changes in the watched directories\n// and attempts to reload all files that changed.\n//\n// Write and Rename events indicate that some files might have changed and reload might be necessary.\n// Remove event indicates that the file was deleted and we should write a warn to log.\n//\n// Reasoning:\n//\n// Write event is sent if the file content is rewritten.\n//\n// Usually files are not rewritten, but they are updated by swapping them with new\n// ones by calling Rename. That avoids files being read while they are not yet\n// completely written but it also means that inotify on file level will not work:\n// watch is invalidated when the old file is deleted.\n//\n// If reading from Kubernetes Secret volumes the target files are symbolic links\n// to files in a different directory. That directory is swapped with a new one,\n// while the symbolic links remain the same. This guarantees atomic swap for all\n// files at once, but it also means any Rename event in the directory might\n// indicate that the files were replaced, even if event.Name is not any of the\n// files we are monitoring. We check the hashes of the files to detect if they\n// were really changed.\nfunc New(filepaths []string, onChange func(), logger *zap.Logger) (*FSWatcher, error) {\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tw := &FSWatcher{\n\t\twatcher:            watcher,\n\t\tlogger:             logger,\n\t\tfileHashContentMap: make(map[string]string),\n\t\tonChange:           onChange,\n\t}\n\n\tif err = w.setupWatchedPaths(filepaths); err != nil {\n\t\terr = errors.Join(err, w.Close())\n\t\treturn nil, err\n\t}\n\n\tgo w.watch()\n\n\treturn w, nil\n}\n\nfunc (w *FSWatcher) setupWatchedPaths(filepaths []string) error {\n\tuniqueDirs := make(map[string]bool)\n\n\tfor _, p := range filepaths {\n\t\tif p == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\th, err := hashFile(p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tw.fileHashContentMap[p] = h\n\t\tdir := path.Dir(p)\n\t\tif _, ok := uniqueDirs[dir]; !ok {\n\t\t\tif err := w.watcher.Add(dir); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tuniqueDirs[dir] = true\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Watch watches for Events and Errors of files.\n// Each time an Event happen, all the files are checked for content change.\n// If a file's content changes, its hashed content is updated and\n// onChange is invoked after all file checks.\nfunc (w *FSWatcher) watch() {\n\tfor {\n\t\tselect {\n\t\tcase event, ok := <-w.watcher.Events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tw.logger.Info(\"Received event\", zap.String(\"event\", event.String()))\n\t\t\tvar changed bool\n\t\t\tw.mu.Lock()\n\t\t\tfor file, hash := range w.fileHashContentMap {\n\t\t\t\tfileChanged, newHash := w.isModified(file, hash)\n\t\t\t\tif fileChanged {\n\t\t\t\t\tchanged = fileChanged\n\t\t\t\t\tw.fileHashContentMap[file] = newHash\n\t\t\t\t}\n\t\t\t}\n\t\t\tw.mu.Unlock()\n\t\t\tif changed {\n\t\t\t\tw.onChange()\n\t\t\t}\n\t\tcase err, ok := <-w.watcher.Errors:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tw.logger.Error(\"fsnotifier reported an error\", zap.Error(err))\n\t\t}\n\t}\n}\n\n// Close closes the watcher.\nfunc (w *FSWatcher) Close() error {\n\treturn w.watcher.Close()\n}\n\n// isModified returns true if the file has been modified since the last check.\nfunc (w *FSWatcher) isModified(filePathName string, previousHash string) (bool, string) {\n\thash, err := hashFile(filePathName)\n\tif err != nil {\n\t\tw.logger.Warn(\"Unable to read the file\", zap.Error(err))\n\t\treturn true, \"\"\n\t}\n\treturn previousHash != hash, hash\n}\n\n// hashFile returns the SHA256 hash of the file.\nfunc hashFile(file string) (string, error) {\n\tf, err := os.Open(filepath.Clean(file)) //nolint:gosec // G703\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer f.Close()\n\n\th := sha256.New()\n\tif _, err := io.Copy(h, f); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn hex.EncodeToString(h.Sum(nil)), nil\n}\n"
  },
  {
    "path": "internal/fswatcher/fswatcher_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage fswatcher\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"go.uber.org/zap/zaptest/observer\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc createTestFiles(t *testing.T) (file1 string, file2 string, file3 string) {\n\ttestDir1 := t.TempDir()\n\n\tfile1 = filepath.Join(testDir1, \"test1.doc\")\n\terr := os.WriteFile(file1, []byte(\"test data\"), 0o600)\n\trequire.NoError(t, err)\n\n\tfile2 = filepath.Join(testDir1, \"test2.doc\")\n\terr = os.WriteFile(file2, []byte(\"test data\"), 0o600)\n\trequire.NoError(t, err)\n\n\ttestDir2 := t.TempDir()\n\n\tfile3 = filepath.Join(testDir2, \"test3.doc\")\n\terr = os.WriteFile(file3, []byte(\"test data\"), 0o600)\n\trequire.NoError(t, err)\n\n\treturn file1, file2, file3\n}\n\nfunc TestFSWatcherAddFiles(t *testing.T) {\n\tfile1, file2, file3 := createTestFiles(t)\n\n\t// Add one unreadable file\n\t_, err := New([]string{\"invalid-file-name\"}, nil, nil)\n\trequire.Error(t, err)\n\n\t// Add one readable file\n\tw, err := New([]string{file1}, nil, nil)\n\trequire.NoError(t, err)\n\tassert.IsType(t, &FSWatcher{}, w)\n\trequire.NoError(t, w.Close())\n\n\t// Add one empty-name file and one readable file\n\tw, err = New([]string{\"\", file1}, nil, nil)\n\trequire.NoError(t, err)\n\tassert.IsType(t, &FSWatcher{}, w)\n\trequire.NoError(t, w.Close())\n\n\t// Add one readable file and one unreadable file\n\t_, err = New([]string{file1, \"invalid-file-name\"}, nil, nil)\n\trequire.Error(t, err)\n\n\t// Add two readable files from one dir\n\tw, err = New([]string{file1, file2}, nil, nil)\n\trequire.NoError(t, err)\n\tassert.IsType(t, &FSWatcher{}, w)\n\trequire.NoError(t, w.Close())\n\n\t// Add two readable files from two different repos\n\tw, err = New([]string{file1, file3}, nil, nil)\n\trequire.NoError(t, err)\n\tassert.IsType(t, &FSWatcher{}, w)\n\trequire.NoError(t, w.Close())\n}\n\nfunc TestFSWatcherWithMultipleFiles(t *testing.T) {\n\ttempDir := t.TempDir()\n\ttestFile1, err := os.Create(tempDir + \"test-file-1\")\n\trequire.NoError(t, err)\n\tdefer testFile1.Close()\n\ttestFile2, err := os.Create(tempDir + \"test-file-2\")\n\trequire.NoError(t, err)\n\tdefer testFile2.Close()\n\t_, err = testFile1.WriteString(\"test content 1\")\n\trequire.NoError(t, err)\n\t_, err = testFile2.WriteString(\"test content 2\")\n\trequire.NoError(t, err)\n\n\tzcore, logObserver := observer.New(zapcore.InfoLevel)\n\tlogger := zap.New(zcore)\n\tonChange := func() {\n\t\tlogger.Info(\"Change happens\")\n\t}\n\n\tw, err := New([]string{testFile1.Name(), testFile2.Name()}, onChange, logger)\n\trequire.NoError(t, err)\n\trequire.IsType(t, &FSWatcher{}, w)\n\tdefer w.Close()\n\n\t// Test Write event\n\ttestFile1.WriteString(\" changed\")\n\ttestFile2.WriteString(\" changed\")\n\tassertLogs(t,\n\t\tfunc() bool {\n\t\t\treturn logObserver.FilterMessage(\"Received event\").Len() > 0\n\t\t},\n\t\t\"Unable to locate 'Received event' in log. All logs: %v\", logObserver)\n\tassertLogs(t,\n\t\tfunc() bool {\n\t\t\treturn logObserver.FilterMessage(\"Change happens\").Len() > 0\n\t\t},\n\t\t\"Unable to locate 'Change happens' in log. All logs: %v\", logObserver)\n\n\t// Wait until the watcher has processed events for BOTH files, not just one.\n\t// The watcher may see the first file's event before the second write completes,\n\t// storing a stale hash for the second file until a subsequent event arrives.\n\tassert.Eventuallyf(t, func() bool {\n\t\th1, _ := hashFile(testFile1.Name())\n\t\th2, _ := hashFile(testFile2.Name())\n\t\tw.mu.RLock()\n\t\tdefer w.mu.RUnlock()\n\t\treturn w.fileHashContentMap[testFile1.Name()] == h1 && w.fileHashContentMap[testFile2.Name()] == h2\n\t}, 10*time.Second, 10*time.Millisecond, \"watcher hashes not updated for both files\")\n\n\t// Test Remove event\n\tos.Remove(testFile1.Name())\n\tos.Remove(testFile2.Name())\n\tassertLogs(t,\n\t\tfunc() bool {\n\t\t\treturn logObserver.FilterMessage(\"Received event\").Len() > 0\n\t\t},\n\t\t\"Unable to locate 'Received event' in log. All logs: %v\", logObserver)\n\n\tassertLogs(t,\n\t\tfunc() bool {\n\t\t\treturn logObserver.FilterMessage(\"Unable to read the file\").Len() >= 2 // Check for multiple occurrences\n\t\t},\n\t\t\"Unable to locate expected 'Unable to read the file' entries in log. All logs: %v\", logObserver)\n}\n\nfunc TestFSWatcherWithSymlinkAndRepoChanges(t *testing.T) {\n\ttestDir := t.TempDir()\n\n\terr := os.Symlink(\"..timestamp-1\", filepath.Join(testDir, \"..data\"))\n\trequire.NoError(t, err)\n\terr = os.Symlink(filepath.Join(\"..data\", \"test.doc\"), filepath.Join(testDir, \"test.doc\"))\n\trequire.NoError(t, err)\n\n\ttimestamp1Dir := filepath.Join(testDir, \"..timestamp-1\")\n\tcreateTimestampDir(t, timestamp1Dir)\n\n\tzcore, logObserver := observer.New(zapcore.InfoLevel)\n\tlogger := zap.New(zcore)\n\n\tonChange := func() {}\n\n\tw, err := New([]string{filepath.Join(testDir, \"test.doc\")}, onChange, logger)\n\trequire.NoError(t, err)\n\trequire.IsType(t, &FSWatcher{}, w)\n\tdefer w.Close()\n\n\ttimestamp2Dir := filepath.Join(testDir, \"..timestamp-2\")\n\tcreateTimestampDir(t, timestamp2Dir)\n\n\terr = os.Symlink(\"..timestamp-2\", filepath.Join(testDir, \"..data_tmp\"))\n\trequire.NoError(t, err)\n\n\tos.Rename(filepath.Join(testDir, \"..data_tmp\"), filepath.Join(testDir, \"..data\"))\n\trequire.NoError(t, err)\n\terr = os.RemoveAll(timestamp1Dir)\n\trequire.NoError(t, err)\n\n\tassertLogs(t,\n\t\tfunc() bool {\n\t\t\treturn logObserver.FilterMessage(\"Received event\").Len() > 0\n\t\t},\n\t\t\"Unable to locate 'Received event' in log. All logs: %v\", logObserver)\n\tbyteData, err := os.ReadFile(filepath.Join(testDir, \"test.doc\"))\n\trequire.NoError(t, err)\n\tassert.Equal(t, byteData, []byte(\"test data\"))\n\n\ttimestamp3Dir := filepath.Join(testDir, \"..timestamp-3\")\n\tcreateTimestampDir(t, timestamp3Dir)\n\terr = os.Symlink(\"..timestamp-3\", filepath.Join(testDir, \"..data_tmp\"))\n\trequire.NoError(t, err)\n\tos.Rename(filepath.Join(testDir, \"..data_tmp\"), filepath.Join(testDir, \"..data\"))\n\trequire.NoError(t, err)\n\terr = os.RemoveAll(timestamp2Dir)\n\trequire.NoError(t, err)\n\n\tassertLogs(t,\n\t\tfunc() bool {\n\t\t\treturn logObserver.FilterMessage(\"Received event\").Len() > 0\n\t\t},\n\t\t\"Unable to locate 'Received event' in log. All logs: %v\", logObserver)\n\tbyteData, err = os.ReadFile(filepath.Join(testDir, \"test.doc\"))\n\trequire.NoError(t, err)\n\tassert.Equal(t, byteData, []byte(\"test data\"))\n}\n\nfunc createTimestampDir(t *testing.T, dir string) {\n\tt.Helper()\n\terr := os.MkdirAll(dir, 0o700)\n\trequire.NoError(t, err)\n\n\terr = os.WriteFile(filepath.Join(dir, \"test.doc\"), []byte(\"test data\"), 0o600)\n\trequire.NoError(t, err)\n}\n\ntype delayedFormat struct {\n\tfn func() any\n}\n\nfunc (df delayedFormat) String() string {\n\treturn fmt.Sprintf(\"%v\", df.fn())\n}\n\nfunc assertLogs(t *testing.T, f func() bool, errorMsg string, logObserver *observer.ObservedLogs) {\n\tassert.Eventuallyf(t, f,\n\t\t10*time.Second, 10*time.Millisecond,\n\t\terrorMsg,\n\t\tdelayedFormat{\n\t\t\tfn: func() any { return logObserver.All() },\n\t\t},\n\t)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/gogocodec/codec.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage gogocodec\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\tgogoproto \"github.com/gogo/protobuf/proto\"\n\t\"google.golang.org/grpc/encoding\"\n\t\"google.golang.org/grpc/encoding/proto\"\n\t\"google.golang.org/grpc/mem\"\n)\n\nconst (\n\tjaegerProtoGenPkgPath = \"github.com/jaegertracing/jaeger-idl/proto-gen\"\n\tjaegerModelPkgPath    = \"github.com/jaegertracing/jaeger-idl/model/v1\"\n\n\tjaegerProtoGenPkgPathOld = \"github.com/jaegertracing/jaeger/internal/proto-gen\"\n\tjaegerModelPkgPathOld    = \"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nvar defaultCodec encoding.CodecV2\n\n// CustomType is an interface that Gogo expects custom types to implement.\n// https://github.com/gogo/protobuf/blob/master/custom_types.md\ntype CustomType interface {\n\tMarshal() ([]byte, error)\n\tMarshalTo(data []byte) (n int, err error)\n\tUnmarshal(data []byte) error\n\n\tgogoproto.Sizer\n\n\tjsonpb.JSONPBMarshaler\n\tjsonpb.JSONPBUnmarshaler\n}\n\nfunc init() {\n\tdefaultCodec = encoding.GetCodecV2(proto.Name)\n\tdefaultCodec.Name() // ensure it's not nil\n\tencoding.RegisterCodecV2(newCodec())\n}\n\n// gogoCodec forces the use of gogo proto marshalling/unmarshalling for\n// Jaeger proto types (package jaeger/gen-proto).\ntype gogoCodec struct{}\n\nvar _ encoding.CodecV2 = (*gogoCodec)(nil)\n\nfunc newCodec() *gogoCodec {\n\treturn &gogoCodec{}\n}\n\n// Name implements encoding.Codec\nfunc (*gogoCodec) Name() string {\n\treturn proto.Name\n}\n\n// Marshal implements encoding.Codec\nfunc (*gogoCodec) Marshal(v any) (mem.BufferSlice, error) {\n\tt := reflect.TypeOf(v)\n\telem := t.Elem()\n\t// use gogo proto only for Jaeger types\n\tif useGogo(elem) {\n\t\tbytes, err := gogoproto.Marshal(v.(gogoproto.Message))\n\t\treturn mem.BufferSlice{mem.SliceBuffer(bytes)}, err\n\t}\n\treturn defaultCodec.Marshal(v)\n}\n\n// Unmarshal implements encoding.Codec\nfunc (*gogoCodec) Unmarshal(data mem.BufferSlice, v any) error {\n\tt := reflect.TypeOf(v)\n\telem := t.Elem() // only for collections\n\t// use gogo proto only for Jaeger types\n\tif useGogo(elem) {\n\t\treturn gogoproto.Unmarshal(data.Materialize(), v.(gogoproto.Message))\n\t}\n\treturn defaultCodec.Unmarshal(data, v)\n}\n\nfunc useGogo(t reflect.Type) bool {\n\tif t == nil {\n\t\treturn false\n\t}\n\tpkg := t.PkgPath()\n\tif strings.HasPrefix(pkg, jaegerProtoGenPkgPath) {\n\t\treturn true\n\t}\n\tif strings.HasPrefix(pkg, jaegerModelPkgPath) {\n\t\treturn true\n\t}\n\tif strings.HasPrefix(pkg, jaegerProtoGenPkgPathOld) {\n\t\treturn true\n\t}\n\tif strings.HasPrefix(pkg, jaegerModelPkgPathOld) {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/gogocodec/codec_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage gogocodec\n\nimport (\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/mem\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestCodecMarshallAndUnmarshall_jaeger_type(t *testing.T) {\n\tc := newCodec()\n\ts1 := &model.Span{OperationName: \"foo\", TraceID: model.NewTraceID(1, 2)}\n\tdata, err := c.Marshal(s1)\n\trequire.NoError(t, err)\n\n\ts2 := &model.Span{}\n\terr = c.Unmarshal(data, s2)\n\trequire.NoError(t, err)\n\tassert.Equal(t, s1, s2)\n}\n\nfunc TestCodecMarshallAndUnmarshall_no_jaeger_type(t *testing.T) {\n\tc := newCodec()\n\tmsg1 := &timestamppb.Timestamp{Seconds: 42, Nanos: 24}\n\tdata, err := c.Marshal(msg1)\n\trequire.NoError(t, err)\n\n\tmsg2 := &timestamppb.Timestamp{}\n\terr = c.Unmarshal(data, msg2)\n\trequire.NoError(t, err)\n\n\t// Marshal function initializes some internal fields in msg1, like sizeCache.\n\t// To ensure the final assert.Equal, do a dummy marshal call on msg2.\n\t_, err = c.Marshal(msg2)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, msg1, msg2)\n}\n\nfunc TestWireCompatibility(t *testing.T) {\n\tc := newCodec()\n\ts1 := &model.Span{OperationName: \"foo\", TraceID: model.NewTraceID(1, 2)}\n\tdata, err := c.Marshal(s1)\n\trequire.NoError(t, err)\n\n\tvar goprotoMessage emptypb.Empty\n\terr = proto.Unmarshal(data.Materialize(), &goprotoMessage)\n\trequire.NoError(t, err)\n\n\tdata2, err := proto.Marshal(&goprotoMessage)\n\trequire.NoError(t, err)\n\n\ts2 := &model.Span{}\n\terr = c.Unmarshal(mem.BufferSlice{mem.SliceBuffer(data2)}, s2)\n\trequire.NoError(t, err)\n\tassert.Equal(t, s1, s2)\n}\n\nfunc TestUseGogo(t *testing.T) {\n\tassert.False(t, useGogo(nil))\n\n\tassert.True(t, useGogo(reflect.TypeFor[model.Span]()))\n}\n\nfunc BenchmarkCodecUnmarshal25Spans(b *testing.B) {\n\tconst fileName = \"../../internal/converter/thrift/jaeger/fixtures/domain_01.json\"\n\tjsonFile, err := os.Open(fileName)\n\trequire.NoError(b, err, \"Failed to open json fixture file %s\", fileName)\n\tvar trace model.Trace\n\trequire.NoError(b, jsonpb.Unmarshal(jsonFile, &trace), fileName)\n\trequire.NotEmpty(b, trace.Spans)\n\tspans := make([]*model.Span, 25)\n\tfor i := range spans {\n\t\tspans[i] = trace.Spans[0]\n\t}\n\ttrace.Spans = spans\n\tc := newCodec()\n\tbytes, err := c.Marshal(&trace)\n\trequire.NoError(b, err)\n\n\tfor b.Loop() {\n\t\tvar trace model.Trace\n\t\terr := c.Unmarshal(bytes, &trace)\n\t\trequire.NoError(b, err)\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/grpctest/reflection.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpctest\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\tgrpcreflection \"google.golang.org/grpc/reflection/grpc_reflection_v1alpha\"\n)\n\n// ReflectionServiceValidator verifies that a gRPC service at a given address\n// supports reflection service. Called must invoke Execute func.\ntype ReflectionServiceValidator struct {\n\tHostPort         string\n\tExpectedServices []string\n}\n\n// Execute performs validation.\nfunc (v ReflectionServiceValidator) Execute(t *testing.T) {\n\tconn, err := grpc.NewClient(\n\t\tv.HostPort,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()))\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\n\tclient := grpcreflection.NewServerReflectionClient(conn)\n\tr, err := client.ServerReflectionInfo(context.Background())\n\trequire.NoError(t, err)\n\trequire.NotNil(t, r)\n\n\terr = r.Send(&grpcreflection.ServerReflectionRequest{\n\t\tMessageRequest: &grpcreflection.ServerReflectionRequest_ListServices{},\n\t})\n\trequire.NoError(t, err)\n\tm, err := r.Recv()\n\trequire.NoError(t, err)\n\trequire.IsType(t,\n\t\tnew(grpcreflection.ServerReflectionResponse_ListServicesResponse),\n\t\tm.MessageResponse)\n\n\tresp := m.MessageResponse.(*grpcreflection.ServerReflectionResponse_ListServicesResponse)\n\tfor _, svc := range v.ExpectedServices {\n\t\tvar found string\n\t\tfor _, s := range resp.ListServicesResponse.Service {\n\t\t\tif svc == s.Name {\n\t\t\t\tfound = s.Name\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\trequire.Equalf(t, svc, found,\n\t\t\t\"service not found, got '%+v'\",\n\t\t\tresp.ListServicesResponse.Service)\n\t}\n}\n"
  },
  {
    "path": "internal/grpctest/reflection_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpctest\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/reflection\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestReflectionServiceValidator(t *testing.T) {\n\tserver := grpc.NewServer()\n\treflection.Register(server)\n\n\tlistener, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tgo func() {\n\t\terr := server.Serve(listener)\n\t\tassert.NoError(t, err)\n\t}()\n\tdefer server.Stop()\n\n\tReflectionServiceValidator{\n\t\tHostPort:         listener.Addr().String(),\n\t\tExpectedServices: []string{\"grpc.reflection.v1alpha.ServerReflection\"},\n\t}.Execute(t)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/gzipfs/gzip.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// Copyright 2021 The Prometheus Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage gzipfs\n\nimport (\n\t\"compress/gzip\"\n\t\"io\"\n\t\"io/fs\"\n\t\"time\"\n)\n\nconst suffix = \".gz\"\n\ntype file struct {\n\tfile    fs.File\n\tcontent []byte\n\toffset  int\n}\n\ntype fileInfo struct {\n\tinfo fs.FileInfo\n\tsize int64\n}\n\ntype fileSystem struct {\n\tfs fs.FS\n}\n\nfunc (f file) Stat() (fs.FileInfo, error) {\n\tstat, err := f.file.Stat()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fileInfo{\n\t\tinfo: stat,\n\t\tsize: int64(len(f.content)),\n\t}, nil\n}\n\nfunc (f *file) Read(buf []byte) (int, error) {\n\tif len(buf) > len(f.content)-f.offset {\n\t\tbuf = buf[0:len(f.content[f.offset:])]\n\t}\n\n\tn := copy(buf, f.content[f.offset:])\n\tif n == len(f.content)-f.offset {\n\t\treturn n, io.EOF\n\t}\n\tf.offset += n\n\treturn n, nil\n}\n\nfunc (f file) Close() error {\n\treturn f.file.Close()\n}\n\nfunc (fi fileInfo) Name() string {\n\tname := fi.info.Name()\n\treturn name[:len(name)-len(suffix)]\n}\n\nfunc (fi fileInfo) Size() int64 { return fi.size }\n\nfunc (fi fileInfo) Mode() fs.FileMode { return fi.info.Mode() }\n\nfunc (fi fileInfo) ModTime() time.Time { return fi.info.ModTime() }\n\nfunc (fi fileInfo) IsDir() bool { return fi.info.IsDir() }\n\nfunc (fileInfo) Sys() any { return nil }\n\n// New wraps underlying fs that is expected to contain gzipped files\n// and presents an unzipped view of it.\nfunc New(fileSys fs.FS) fs.FS {\n\treturn fileSystem{fileSys}\n}\n\nfunc (cfs fileSystem) Open(path string) (fs.File, error) {\n\tvar f fs.File\n\tf, err := cfs.fs.Open(path)\n\tif err == nil {\n\t\treturn f, nil\n\t}\n\n\tf, err = cfs.fs.Open(path + suffix)\n\tif err != nil {\n\t\treturn f, err\n\t}\n\n\tgr, err := gzip.NewReader(f)\n\tif err != nil {\n\t\treturn f, err\n\t}\n\tdefer gr.Close()\n\n\tc, err := io.ReadAll(gr)\n\tif err != nil {\n\t\treturn f, err\n\t}\n\n\treturn &file{\n\t\tfile:    f,\n\t\tcontent: c,\n\t}, nil\n}\n"
  },
  {
    "path": "internal/gzipfs/gzip_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// Copyright 2021 The Prometheus Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage gzipfs\n\nimport (\n\t\"embed\"\n\t\"io\"\n\t\"io/fs\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\n//go:embed testdata\nvar EmbedFS embed.FS\n\nvar testFS = New(EmbedFS)\n\ntype mockFile struct {\n\terr error\n}\n\nfunc (f *mockFile) Stat() (fs.FileInfo, error) {\n\treturn nil, f.err\n}\n\nfunc (f *mockFile) Read([]byte) (int, error) {\n\treturn 0, f.err\n}\n\nfunc (f *mockFile) Close() error {\n\treturn f.err\n}\n\nfunc TestFS(t *testing.T) {\n\tcases := []struct {\n\t\tname            string\n\t\tpath            string\n\t\texpectedErr     string\n\t\texpectedName    string\n\t\texpectedMode    fs.FileMode\n\t\texpectedSize    int64\n\t\texpectedContent string\n\t\texpectedModTime time.Time\n\t}{\n\t\t{\n\t\t\tname:            \"uncompressed file\",\n\t\t\tpath:            \"testdata/foobar\",\n\t\t\texpectedMode:    0o444,\n\t\t\texpectedName:    \"foobar\",\n\t\t\texpectedSize:    11,\n\t\t\texpectedContent: \"hello world\",\n\t\t\texpectedModTime: time.Date(1, 1, 1, 0, 0, 0, 0 /* nanos */, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:            \"compressed file\",\n\t\t\tpath:            \"testdata/foobar.gz\",\n\t\t\texpectedMode:    0o444,\n\t\t\texpectedName:    \"foobar.gz\",\n\t\t\texpectedSize:    38,\n\t\t\texpectedContent: \"\", // actual gzipped payload is returned\n\t\t\texpectedModTime: time.Date(1, 1, 1, 0, 0, 0, 0 /* nanos */, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:            \"compressed file accessed without gz extension\",\n\t\t\tpath:            \"testdata/foobaz\",\n\t\t\texpectedMode:    0o444,\n\t\t\texpectedName:    \"foobaz\",\n\t\t\texpectedSize:    11,\n\t\t\texpectedContent: \"hello world\",\n\t\t\texpectedModTime: time.Date(1, 1, 1, 0, 0, 0, 0 /* nanos */, time.UTC),\n\t\t},\n\t\t{\n\t\t\tname:        \"non-existing file\",\n\t\t\tpath:        \"testdata/non-existing-file\",\n\t\t\texpectedErr: \"file does not exist\",\n\t\t},\n\t\t{\n\t\t\tname:        \"not gzipped file\",\n\t\t\tpath:        \"testdata/not_archive\",\n\t\t\texpectedErr: \"invalid header\",\n\t\t},\n\t\t{\n\t\t\t// To provide coverage of the error from io.ReadAll function, we use a file\n\t\t\t// that is a copy of proper gzipped file testdata/foobaz.gz but truncated\n\t\t\t// to 36 bytes with:\n\t\t\t//     perl -e \"truncate 'internal/gzipfs/testdata/foobaz_truncated.gz', 36\"\n\t\t\t// This allows gzip.NewReader() to succeed because the file has a proper gz\n\t\t\t// header, but subsequent read fails with unexpected EOF.\n\t\t\tname:        \"compressed but truncated file accessed without gz extension\",\n\t\t\tpath:        \"testdata/foobaz_truncated\",\n\t\t\texpectedErr: \"unexpected EOF\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tf, err := testFS.Open(c.path)\n\t\t\tif c.expectedErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, c.expectedErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer f.Close()\n\n\t\t\tstat, err := f.Stat()\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Equal(t, c.expectedName, stat.Name())\n\t\t\tassert.Equal(t, c.expectedMode, stat.Mode())\n\t\t\tassert.Equal(t, c.expectedSize, stat.Size())\n\t\t\tassert.Equal(t, c.expectedModTime, stat.ModTime())\n\t\t\tassert.False(t, stat.IsDir())\n\t\t\tassert.Nil(t, stat.Sys())\n\t\t\tcontent, err := io.ReadAll(f)\n\t\t\trequire.NoError(t, err)\n\t\t\tif c.expectedContent != \"\" {\n\t\t\t\tassert.Equal(t, c.expectedContent, string(content))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFileStatError(t *testing.T) {\n\tf := &file{file: &mockFile{assert.AnError}}\n\t_, err := f.Stat()\n\tassert.Equal(t, assert.AnError, err)\n}\n\nfunc TestFileRead(t *testing.T) {\n\tf := &file{content: []byte(\"long content\")}\n\tbuf := make([]byte, 5) // shorter buffer\n\tn, err := f.Read(buf)\n\trequire.NoError(t, err)\n\tassert.Equal(t, 5, n)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/gzipfs/testdata/foobar",
    "content": "hello world"
  },
  {
    "path": "internal/hostname/hostname.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage hostname\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n)\n\ntype hostname struct {\n\tonce     sync.Once\n\terr      error\n\thostname string\n}\n\nvar h hostname\n\n// AsIdentifier uses the hostname of the os and postfixes a short random string to guarantee uniqueness\n// The returned value is appropriate to use as a convenient unique identifier and will always be equal\n// when called from within the same process.\nfunc AsIdentifier() (string, error) {\n\th.once.Do(func() {\n\t\th.hostname, h.err = os.Hostname()\n\t\tif h.err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tbuff := make([]byte, 8)\n\t\t_, h.err = rand.Read(buff)\n\t\tif h.err != nil {\n\t\t\treturn\n\t\t}\n\n\t\th.hostname = h.hostname + \"-\" + fmt.Sprintf(\"%2x\", buff)\n\t})\n\n\treturn h.hostname, h.err\n}\n"
  },
  {
    "path": "internal/hostname/hostname_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage hostname\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestAsIdentifier(t *testing.T) {\n\tvar hostname1 string\n\tvar hostname2 string\n\n\tvar wg sync.WaitGroup\n\twg.Go(func() {\n\t\tvar err error\n\t\thostname1, err = AsIdentifier()\n\t\tassert.NoError(t, err)\n\t})\n\twg.Go(func() {\n\t\tvar err error\n\t\thostname2, err = AsIdentifier()\n\t\tassert.NoError(t, err)\n\t})\n\twg.Wait()\n\n\tactualHostname, _ := os.Hostname()\n\tassert.Equal(t, hostname1, hostname2)\n\tassert.True(t, strings.HasPrefix(hostname1, actualHostname))\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/httpfs/prefixed.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage httpfs\n\nimport (\n\t\"net/http\"\n)\n\n// PrefixedFS returns a FileSystem that adds a path prefix to all files\n// before delegating to the underlying fs.\nfunc PrefixedFS(prefix string, fs http.FileSystem) http.FileSystem {\n\treturn &prefixedFS{\n\t\tprefix: prefix,\n\t\tfs:     fs,\n\t}\n}\n\ntype prefixedFS struct {\n\tprefix string\n\tfs     http.FileSystem\n}\n\nfunc (fs *prefixedFS) Open(name string) (http.File, error) {\n\tprefixedName := fs.prefix + name\n\tif name == \"/\" {\n\t\t// Return the dir itself when asked for the root.\n\t\t// This is what http.FS() also does to allow redirects\n\t\t// from `/`` to `/index.html`.\n\t\tprefixedName = fs.prefix\n\t}\n\treturn fs.fs.Open(prefixedName)\n}\n"
  },
  {
    "path": "internal/httpfs/prefixed_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage httpfs\n\nimport (\n\t\"embed\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\n//go:embed test_assets/*\nvar assetFS embed.FS\n\nfunc TestPrefixedFS(t *testing.T) {\n\tfs := PrefixedFS(\"test_assets\", http.FS(assetFS))\n\ttests := []struct {\n\t\tfile  string\n\t\tisDir bool\n\t}{\n\t\t{file: \"/\", isDir: true},\n\t\t{file: \"/somefile.txt\", isDir: false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.file, func(t *testing.T) {\n\t\t\tfile, err := fs.Open(tt.file)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, file)\n\t\t\tstat, err := file.Stat()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tt.isDir, stat.IsDir())\n\t\t})\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/httpfs/test_assets/somefile.txt",
    "content": ""
  },
  {
    "path": "internal/jaegerclientenv2otel/envvars.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaegerclientenv2otel\n\nimport (\n\t\"os\"\n\n\t\"go.uber.org/zap\"\n)\n\n//nolint:gosec // G101 - env var names, not credentials\nvar envVars = map[string]string{\n\t\"JAEGER_SERVICE_NAME\":                           \"\",\n\t\"JAEGER_AGENT_HOST\":                             \"OTEL_EXPORTER_JAEGER_AGENT_HOST\",\n\t\"JAEGER_AGENT_PORT\":                             \"OTEL_EXPORTER_JAEGER_AGENT_PORT\",\n\t\"JAEGER_ENDPOINT\":                               \"OTEL_EXPORTER_JAEGER_ENDPOINT\",\n\t\"JAEGER_USER\":                                   \"OTEL_EXPORTER_JAEGER_USER\",\n\t\"JAEGER_PASSWORD\":                               \"OTEL_EXPORTER_JAEGER_PASSWORD\",\n\t\"JAEGER_REPORTER_LOG_SPANS\":                     \"\",\n\t\"JAEGER_REPORTER_MAX_QUEUE_SIZE\":                \"\",\n\t\"JAEGER_REPORTER_FLUSH_INTERVAL\":                \"\",\n\t\"JAEGER_REPORTER_ATTEMPT_RECONNECTING_DISABLED\": \"\",\n\t\"JAEGER_REPORTER_ATTEMPT_RECONNECT_INTERVAL\":    \"\",\n\t\"JAEGER_SAMPLER_TYPE\":                           \"\",\n\t\"JAEGER_SAMPLER_PARAM\":                          \"\",\n\t\"JAEGER_SAMPLER_MANAGER_HOST_PORT\":              \"\",\n\t\"JAEGER_SAMPLING_ENDPOINT\":                      \"\",\n\t\"JAEGER_SAMPLER_MAX_OPERATIONS\":                 \"\",\n\t\"JAEGER_SAMPLER_REFRESH_INTERVAL\":               \"\",\n\t\"JAEGER_TAGS\":                                   \"\",\n\t\"JAEGER_TRACEID_128BIT\":                         \"\",\n\t\"JAEGER_DISABLED\":                               \"\",\n\t\"JAEGER_RPC_METRICS\":                            \"\",\n}\n\nfunc MapJaegerToOtelEnvVars(logger *zap.Logger) {\n\tfor jname, otelname := range envVars {\n\t\tval := os.Getenv(jname)\n\t\tif val == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif otelname == \"\" {\n\t\t\tlogger.Sugar().Infof(\"Ignoring deprecated Jaeger SDK env var %s, as there is no equivalent in OpenTelemetry\", jname)\n\t\t} else {\n\t\t\tos.Setenv(otelname, val)\n\t\t\tlogger.Sugar().Infof(\"Replacing deprecated Jaeger SDK env var %s with OpenTelemetry env var %s\", jname, otelname)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/jaegerclientenv2otel/envvars_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jaegerclientenv2otel\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMapJaegerToOtelEnvVars(t *testing.T) {\n\tt.Setenv(\"JAEGER_TAGS\", \"tags\")\n\tt.Setenv(\"JAEGER_USER\", \"user\")\n\n\tlogger, buffer := testutils.NewLogger()\n\tMapJaegerToOtelEnvVars(logger)\n\n\tassert.Equal(t, \"user\", os.Getenv(\"OTEL_EXPORTER_JAEGER_USER\"))\n\tassert.Contains(t, buffer.String(), \"Replacing deprecated Jaeger SDK env var JAEGER_USER with OpenTelemetry env var OTEL_EXPORTER_JAEGER_USER\")\n\tassert.Contains(t, buffer.String(), \"Ignoring deprecated Jaeger SDK env var JAEGER_TAGS\")\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/jiter/iter.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package iter is a backport of Go 1.23 official \"iter\" package, until we upgrade.\npackage jiter\n\nimport (\n\t\"iter\"\n)\n\nfunc CollectWithErrors[V any](seq iter.Seq2[V, error]) ([]V, error) {\n\tvar result []V\n\tfor v, err := range seq {\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult = append(result, v)\n\t}\n\treturn result, nil\n}\n\nfunc FlattenWithErrors[V any](seq iter.Seq2[[]V, error]) ([]V, error) {\n\tvar result []V\n\tfor v, err := range seq {\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult = append(result, v...)\n\t}\n\treturn result, nil\n}\n"
  },
  {
    "path": "internal/jiter/iter_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jiter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCollectWithErrors(t *testing.T) {\n\ttype item struct {\n\t\tstr string\n\t\terr error\n\t}\n\ttests := []struct {\n\t\tname     string\n\t\titems    []item\n\t\texpected []string\n\t\terr      error\n\t}{\n\t\t{\n\t\t\tname: \"no errors\",\n\t\t\titems: []item{\n\t\t\t\t{str: \"a\", err: nil},\n\t\t\t\t{str: \"b\", err: nil},\n\t\t\t\t{str: \"c\", err: nil},\n\t\t\t},\n\t\t\texpected: []string{\"a\", \"b\", \"c\"},\n\t\t},\n\t\t{\n\t\t\tname: \"first error\",\n\t\t\titems: []item{\n\t\t\t\t{str: \"a\", err: nil},\n\t\t\t\t{str: \"b\", err: nil},\n\t\t\t\t{str: \"c\", err: assert.AnError},\n\t\t\t},\n\t\t\terr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"second error\",\n\t\t\titems: []item{\n\t\t\t\t{str: \"a\", err: nil},\n\t\t\t\t{str: \"b\", err: assert.AnError},\n\t\t\t\t{str: \"c\", err: nil},\n\t\t\t},\n\t\t\terr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"third error\",\n\t\t\titems: []item{\n\t\t\t\t{str: \"a\", err: nil},\n\t\t\t\t{str: \"b\", err: nil},\n\t\t\t\t{str: \"c\", err: assert.AnError},\n\t\t\t},\n\n\t\t\terr: assert.AnError,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tseq := func(yield func(string, error) bool) {\n\t\t\t\tfor _, item := range test.items {\n\t\t\t\t\tif !yield(item.str, item.err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult, err := CollectWithErrors(seq)\n\t\t\tif test.err != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, test.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFlattenWithErrors(t *testing.T) {\n\ttype item struct {\n\t\tstrs []string\n\t\terr  error\n\t}\n\ttests := []struct {\n\t\tname     string\n\t\titems    []item\n\t\texpected []string\n\t\terr      error\n\t}{\n\t\t{\n\t\t\tname: \"no errors\",\n\t\t\titems: []item{\n\t\t\t\t{strs: []string{\"a\", \"b\", \"c\"}, err: nil},\n\t\t\t\t{strs: []string{\"d\", \"e\", \"f\"}, err: nil},\n\t\t\t\t{strs: []string{\"g\", \"h\", \"i\"}, err: nil},\n\t\t\t},\n\t\t\texpected: []string{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\"},\n\t\t},\n\t\t{\n\t\t\tname: \"first error\",\n\t\t\titems: []item{\n\t\t\t\t{strs: []string{\"a\", \"b\", \"c\"}, err: nil},\n\t\t\t\t{strs: []string{\"d\", \"e\", \"f\"}, err: assert.AnError},\n\t\t\t\t{strs: []string{\"g\", \"h\", \"i\"}, err: nil},\n\t\t\t},\n\t\t\terr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"second error\",\n\t\t\titems: []item{\n\t\t\t\t{strs: []string{\"a\", \"b\", \"c\"}, err: nil},\n\t\t\t\t{strs: []string{\"d\", \"e\", \"f\"}, err: nil},\n\t\t\t\t{strs: []string{\"g\", \"h\", \"i\"}, err: assert.AnError},\n\t\t\t},\n\t\t\terr: assert.AnError,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tseq := func(yield func([]string, error) bool) {\n\t\t\t\tfor _, item := range test.items {\n\t\t\t\t\tif !yield(item.strs, item.err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult, err := FlattenWithErrors(seq)\n\t\t\tif test.err != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, test.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/jiter/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jiter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/jptrace/aggregator.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"fmt\"\n\t\"iter\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\n// AggregateTraces aggregates a sequence of trace batches into individual traces.\n//\n// The `tracesSeq` input must adhere to the chunking requirements of tracestore.Reader.GetTraces.\nfunc AggregateTraces(tracesSeq iter.Seq2[[]ptrace.Traces, error]) iter.Seq2[ptrace.Traces, error] {\n\treturn AggregateTracesWithLimit(tracesSeq, 0)\n}\n\n// AggregateTracesWithLimit aggregates a sequence of trace batches into individual traces\n// but limits each trace size to maxSize spans. If maxSize is 0 or negative, there is no\n// limit and all spans will be included in the aggregated trace.\n//\n// The `tracesSeq` input must adhere to the chunking requirements of tracestore.Reader.GetTraces.\nfunc AggregateTracesWithLimit(tracesSeq iter.Seq2[[]ptrace.Traces, error], maxSize int) iter.Seq2[ptrace.Traces, error] {\n\treturn func(yield func(trace ptrace.Traces, err error) bool) {\n\t\tcurrentTrace := ptrace.NewTraces()\n\t\tcurrentTraceID := pcommon.NewTraceIDEmpty()\n\t\tcurrentSpanCount := 0\n\t\tcurrentTruncated := false\n\t\tcont := true\n\n\t\ttracesSeq(func(traces []ptrace.Traces, err error) bool {\n\t\t\tif err != nil {\n\t\t\t\tcont = yield(ptrace.NewTraces(), err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfor _, trace := range traces {\n\t\t\t\tincomingSpanCount := trace.SpanCount()\n\t\t\t\tif incomingSpanCount == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttraceID := GetTraceID(trace)\n\t\t\t\tif currentTraceID == traceID {\n\t\t\t\t\t// same trace as current, merge it into the current trace, respecting the maxSize limit\n\t\t\t\t\tvar truncated bool\n\t\t\t\t\tcurrentSpanCount, truncated = mergeTracesWithLimit(currentTrace, currentSpanCount, trace, incomingSpanCount, maxSize)\n\t\t\t\t\tif truncated && !currentTruncated {\n\t\t\t\t\t\tmarkTraceTruncated(currentTrace, maxSize)\n\t\t\t\t\t\tcurrentTruncated = true\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif currentSpanCount > 0 {\n\t\t\t\t\t\tif !yield(currentTrace, nil) {\n\t\t\t\t\t\t\tcont = false\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcurrentTraceID = traceID\n\t\t\t\t\tcurrentTruncated = false\n\t\t\t\t\tif maxSize > 0 && incomingSpanCount > maxSize {\n\t\t\t\t\t\tcurrentTrace = ptrace.NewTraces()\n\t\t\t\t\t\tcopySpansUpToLimit(currentTrace, trace, maxSize)\n\t\t\t\t\t\tcurrentSpanCount = maxSize\n\t\t\t\t\t\tmarkTraceTruncated(currentTrace, maxSize)\n\t\t\t\t\t\tcurrentTruncated = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Optimization: when incoming trace fits within the limit (or there is no limit),\n\t\t\t\t\t\t// we can skip the copy and use it directly as the current trace.\n\t\t\t\t\t\tcurrentTrace = trace\n\t\t\t\t\t\tcurrentSpanCount = incomingSpanCount\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\t// Emit the last accumulated trace if non-empty.\n\t\t// `cont` guards against calling yield after consumer already returned false.\n\t\tif cont && currentSpanCount > 0 {\n\t\t\tyield(currentTrace, nil)\n\t\t}\n\t}\n}\n\n// mergeTracesWithLimit merges src into dest, respecting the maxSize span limit.\n// destCount and srcCount are the pre-computed span counts for dest and src respectively.\n// Returns the updated span count of dest and whether the trace was truncated (true if\n// src spans were dropped due to the limit). If maxSize <= 0, all spans are merged without limit.\nfunc mergeTracesWithLimit(dest ptrace.Traces, destCount int, src ptrace.Traces, srcCount int, maxSize int) (int, bool) {\n\t// early exit if already at max\n\tif maxSize > 0 && destCount >= maxSize {\n\t\treturn destCount, true\n\t}\n\n\t// check if we can merge all spans without exceeding limit\n\tif maxSize <= 0 || destCount+srcCount <= maxSize {\n\t\tMergeTraces(dest, src)\n\t\treturn destCount + srcCount, false\n\t}\n\n\t// partial copy: only copy the spans that fit\n\tcopySpansUpToLimit(dest, src, maxSize-destCount)\n\treturn maxSize, true\n}\n\nfunc copySpansUpToLimit(dest, src ptrace.Traces, limit int) {\n\tcopied := 0\n\n\tfor _, srcResource := range src.ResourceSpans().All() {\n\t\tif copied >= limit {\n\t\t\treturn\n\t\t}\n\t\tvar destResource ptrace.ResourceSpans\n\t\tresourceAdded := false\n\n\t\tfor _, srcScope := range srcResource.ScopeSpans().All() {\n\t\t\tif copied >= limit {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar destScope ptrace.ScopeSpans\n\t\t\tscopeAdded := false\n\n\t\t\tfor _, span := range srcScope.Spans().All() {\n\t\t\t\tif copied >= limit {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Lazily create resource and scope containers only when a span is actually copied,\n\t\t\t\t// to avoid leaving empty container artifacts in dest.\n\t\t\t\tif !resourceAdded {\n\t\t\t\t\tdestResource = dest.ResourceSpans().AppendEmpty()\n\t\t\t\t\tsrcResource.Resource().CopyTo(destResource.Resource())\n\t\t\t\t\tdestResource.SetSchemaUrl(srcResource.SchemaUrl())\n\t\t\t\t\tresourceAdded = true\n\t\t\t\t}\n\t\t\t\tif !scopeAdded {\n\t\t\t\t\tdestScope = destResource.ScopeSpans().AppendEmpty()\n\t\t\t\t\tsrcScope.Scope().CopyTo(destScope.Scope())\n\t\t\t\t\tdestScope.SetSchemaUrl(srcScope.SchemaUrl())\n\t\t\t\t\tscopeAdded = true\n\t\t\t\t}\n\t\t\t\tspan.CopyTo(destScope.Spans().AppendEmpty())\n\t\t\t\tcopied++\n\t\t\t}\n\t\t}\n\t}\n}\n\n// MergeTraces merges src trace into dest trace.\n// This is useful when multiple iterations return parts of the same trace.\nfunc MergeTraces(dest, src ptrace.Traces) {\n\tresources := src.ResourceSpans()\n\tfor i := 0; i < resources.Len(); i++ {\n\t\tresource := resources.At(i)\n\t\tresource.CopyTo(dest.ResourceSpans().AppendEmpty())\n\t}\n}\n\nfunc markTraceTruncated(trace ptrace.Traces, maxSize int) {\n\tfor _, span := range SpanIter(trace) {\n\t\tAddWarnings(\n\t\t\tspan,\n\t\t\tfmt.Sprintf(\"trace has more than %d spans, showing first %d spans only\", maxSize, maxSize),\n\t\t)\n\t\treturn // stop after first span\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/aggregator_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestAggregateTraces_AggregatesSpansWithSameTraceID(t *testing.T) {\n\ttrace1 := ptrace.NewTraces()\n\tresource1 := trace1.ResourceSpans().AppendEmpty()\n\tscope1 := resource1.ScopeSpans().AppendEmpty()\n\tspan1 := scope1.Spans().AppendEmpty()\n\tspan1.SetTraceID(pcommon.TraceID([16]byte{1}))\n\tspan1.SetName(\"span1\")\n\n\ttrace1Continued := ptrace.NewTraces()\n\tresource2 := trace1Continued.ResourceSpans().AppendEmpty()\n\tscope2 := resource2.ScopeSpans().AppendEmpty()\n\tspan2 := scope2.Spans().AppendEmpty()\n\tspan2.SetTraceID(pcommon.TraceID([16]byte{1}))\n\tspan2.SetName(\"span2\")\n\n\ttrace2 := ptrace.NewTraces()\n\tresource3 := trace2.ResourceSpans().AppendEmpty()\n\tscope3 := resource3.ScopeSpans().AppendEmpty()\n\tspan3 := scope3.Spans().AppendEmpty()\n\tspan3.SetTraceID(pcommon.TraceID([16]byte{2}))\n\tspan3.SetName(\"span3\")\n\n\ttrace3 := ptrace.NewTraces()\n\tresource4 := trace3.ResourceSpans().AppendEmpty()\n\tscope4 := resource4.ScopeSpans().AppendEmpty()\n\tspan4 := scope4.Spans().AppendEmpty()\n\tspan4.SetTraceID(pcommon.TraceID([16]byte{3}))\n\tspan4.SetName(\"span4\")\n\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tyield([]ptrace.Traces{trace1, trace1Continued, trace2}, nil)\n\t\tyield([]ptrace.Traces{trace3}, nil)\n\t}\n\n\tvar result []ptrace.Traces\n\tAggregateTraces(tracesSeq)(func(trace ptrace.Traces, _ error) bool {\n\t\tresult = append(result, trace)\n\t\treturn true\n\t})\n\n\trequire.Len(t, result, 3)\n\n\trequire.Equal(t, 2, result[0].ResourceSpans().Len())\n\trequire.Equal(t, 1, result[1].ResourceSpans().Len())\n\trequire.Equal(t, 1, result[2].ResourceSpans().Len())\n\n\tgotSpan1 := result[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\trequire.Equal(t, gotSpan1.TraceID(), pcommon.TraceID([16]byte{1}))\n\trequire.Equal(t, \"span1\", gotSpan1.Name())\n\n\tgotSpan2 := result[0].ResourceSpans().At(1).ScopeSpans().At(0).Spans().At(0)\n\trequire.Equal(t, gotSpan2.TraceID(), pcommon.TraceID([16]byte{1}))\n\trequire.Equal(t, \"span2\", gotSpan2.Name())\n\n\tgotSpan3 := result[1].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\trequire.Equal(t, gotSpan3.TraceID(), pcommon.TraceID([16]byte{2}))\n\trequire.Equal(t, \"span3\", gotSpan3.Name())\n\n\tgotSpan4 := result[2].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\trequire.Equal(t, gotSpan4.TraceID(), pcommon.TraceID([16]byte{3}))\n\trequire.Equal(t, \"span4\", gotSpan4.Name())\n}\n\nfunc TestAggregateTraces_YieldsErrorFromTracesSeq(t *testing.T) {\n\ttrace1 := ptrace.NewTraces()\n\tresource1 := trace1.ResourceSpans().AppendEmpty()\n\tscope1 := resource1.ScopeSpans().AppendEmpty()\n\tspan1 := scope1.Spans().AppendEmpty()\n\tspan1.SetTraceID(pcommon.TraceID([16]byte{1}))\n\tspan1.SetName(\"span1\")\n\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tif !yield(nil, assert.AnError) {\n\t\t\treturn\n\t\t}\n\t\tyield([]ptrace.Traces{trace1}, nil) // should not get here\n\t}\n\taggregatedSeq := AggregateTraces(tracesSeq)\n\n\tvar lastResult ptrace.Traces\n\tvar lastErr error\n\taggregatedSeq(func(trace ptrace.Traces, e error) bool {\n\t\tlastResult = trace\n\t\tif e != nil {\n\t\t\tlastErr = e\n\t\t}\n\t\treturn true\n\t})\n\n\trequire.ErrorIs(t, lastErr, assert.AnError)\n\trequire.Equal(t, ptrace.NewTraces(), lastResult)\n}\n\nfunc TestAggregateTraces_RespectsEarlyReturn(t *testing.T) {\n\ttrace1 := ptrace.NewTraces()\n\tresource1 := trace1.ResourceSpans().AppendEmpty()\n\tscope1 := resource1.ScopeSpans().AppendEmpty()\n\tspan1 := scope1.Spans().AppendEmpty()\n\tspan1.SetTraceID(pcommon.TraceID([16]byte{1}))\n\tspan1.SetName(\"span1\")\n\n\ttrace2 := ptrace.NewTraces()\n\tresource2 := trace2.ResourceSpans().AppendEmpty()\n\tscope2 := resource2.ScopeSpans().AppendEmpty()\n\tspan2 := scope2.Spans().AppendEmpty()\n\tspan2.SetTraceID(pcommon.TraceID([16]byte{2}))\n\tspan2.SetName(\"span2\")\n\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tyield([]ptrace.Traces{trace1}, nil)\n\t\tyield([]ptrace.Traces{trace2}, nil)\n\t}\n\taggregatedSeq := AggregateTraces(tracesSeq)\n\n\tvar lastResult ptrace.Traces\n\taggregatedSeq(func(trace ptrace.Traces, _ error) bool {\n\t\tlastResult = trace\n\t\treturn false\n\t})\n\n\trequire.Equal(t, trace1, lastResult)\n}\n\nfunc TestAggregateTracesWithLimit(t *testing.T) {\n\tcreateTrace := func(traceID byte, spanCount int) ptrace.Traces {\n\t\ttrace := ptrace.NewTraces()\n\t\tspans := trace.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\t\tfor i := 0; i < spanCount; i++ {\n\t\t\tspan := spans.AppendEmpty()\n\t\t\tspan.SetTraceID(pcommon.TraceID([16]byte{traceID}))\n\t\t}\n\t\treturn trace\n\t}\n\n\ttests := []struct {\n\t\tname           string\n\t\tmaxSize        int\n\t\tinputSpans     int\n\t\texpectedSpans  int\n\t\texpectTruncate bool\n\t}{\n\t\t{\"no_limit\", 0, 5, 5, false},\n\t\t{\"under_limit\", 10, 5, 5, false},\n\t\t{\"over_limit\", 3, 5, 3, true},\n\t\t{\"exact_limit\", 5, 5, 5, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\tyield([]ptrace.Traces{createTrace(1, tt.inputSpans)}, nil)\n\t\t\t}\n\n\t\t\tvar result []ptrace.Traces\n\t\t\tAggregateTracesWithLimit(tracesSeq, tt.maxSize)(func(trace ptrace.Traces, _ error) bool {\n\t\t\t\tresult = append(result, trace)\n\t\t\t\treturn true\n\t\t\t})\n\n\t\t\trequire.Len(t, result, 1)\n\t\t\tassert.Equal(t, tt.expectedSpans, result[0].SpanCount())\n\n\t\t\t// Check for truncation warning\n\t\t\tif tt.expectTruncate {\n\t\t\t\tfirstSpan := result[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\t\t\t\twarnings := GetWarnings(firstSpan)\n\t\t\t\tassert.NotEmpty(t, warnings, \"expected truncation warning\")\n\t\t\t\tassert.Contains(t, warnings[len(warnings)-1], fmt.Sprintf(\"trace has more than %d spans\", tt.maxSize))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCopySpansUpToLimit(t *testing.T) {\n\tsrc := ptrace.NewTraces()\n\tspans := src.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\tfor i := 0; i < 5; i++ {\n\t\tspans.AppendEmpty().SetName(\"span\")\n\t}\n\n\tdest := ptrace.NewTraces()\n\tcopySpansUpToLimit(dest, src, 3)\n\n\tassert.Equal(t, 3, dest.SpanCount())\n}\n\nfunc TestCopySpansUpToLimit_MultipleResourceSpans(t *testing.T) {\n\tsrc := ptrace.NewTraces()\n\trs0 := src.ResourceSpans().AppendEmpty()\n\tss0 := rs0.ScopeSpans().AppendEmpty()\n\tss0.Spans().AppendEmpty().SetName(\"rs0-span0\")\n\tss0.Spans().AppendEmpty().SetName(\"rs0-span1\")\n\trs1 := src.ResourceSpans().AppendEmpty()\n\tss1 := rs1.ScopeSpans().AppendEmpty()\n\tss1.Spans().AppendEmpty().SetName(\"rs1-span0\")\n\tss1.Spans().AppendEmpty().SetName(\"rs1-span1\")\n\n\tdest := ptrace.NewTraces()\n\tcopySpansUpToLimit(dest, src, 3)\n\n\trequire.Equal(t, 3, dest.SpanCount())\n\trequire.Equal(t, 2, dest.ResourceSpans().Len())\n\tassert.Equal(t, 2, dest.ResourceSpans().At(0).ScopeSpans().At(0).Spans().Len())\n\tassert.Equal(t, 1, dest.ResourceSpans().At(1).ScopeSpans().At(0).Spans().Len())\n}\n\nfunc TestCopySpansUpToLimit_MultipleScopeSpans(t *testing.T) {\n\tsrc := ptrace.NewTraces()\n\trs := src.ResourceSpans().AppendEmpty()\n\tss0 := rs.ScopeSpans().AppendEmpty()\n\tss0.Spans().AppendEmpty().SetName(\"ss0-span0\")\n\tss0.Spans().AppendEmpty().SetName(\"ss0-span1\")\n\tss1 := rs.ScopeSpans().AppendEmpty()\n\tss1.Spans().AppendEmpty().SetName(\"ss1-span0\")\n\tss1.Spans().AppendEmpty().SetName(\"ss1-span1\")\n\n\tdest := ptrace.NewTraces()\n\tcopySpansUpToLimit(dest, src, 3)\n\n\trequire.Equal(t, 3, dest.SpanCount())\n\trequire.Equal(t, 1, dest.ResourceSpans().Len())\n\tdestScopes := dest.ResourceSpans().At(0).ScopeSpans()\n\trequire.Equal(t, 2, destScopes.Len())\n\tassert.Equal(t, 2, destScopes.At(0).Spans().Len())\n\tassert.Equal(t, 1, destScopes.At(1).Spans().Len())\n}\n\nfunc TestCopySpansUpToLimit_NoEmptyContainers(t *testing.T) {\n\t// src has two resources: the first has no scopes, the second has spans.\n\t// copySpansUpToLimit should not create an empty ResourceSpans for the first resource.\n\tsrc := ptrace.NewTraces()\n\tsrc.ResourceSpans().AppendEmpty() // empty resource, no scopes\n\tspans := src.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\tfor i := 0; i < 3; i++ {\n\t\tspans.AppendEmpty().SetName(\"span\")\n\t}\n\n\tdest := ptrace.NewTraces()\n\tcopySpansUpToLimit(dest, src, 2)\n\n\tassert.Equal(t, 2, dest.SpanCount())\n\tassert.Equal(t, 1, dest.ResourceSpans().Len(), \"empty resource should not be copied\")\n}\n\nfunc TestAggregateTracesWithLimit_MultiBatch(t *testing.T) {\n\t// A trace that arrives in three batches should produce exactly one truncation\n\t// warning even when subsequent batches arrive after the limit is already reached.\n\tcreateBatch := func(traceID byte, spanCount int) ptrace.Traces {\n\t\ttrace := ptrace.NewTraces()\n\t\tspans := trace.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\t\tfor i := 0; i < spanCount; i++ {\n\t\t\tspan := spans.AppendEmpty()\n\t\t\tspan.SetTraceID(pcommon.TraceID([16]byte{traceID}))\n\t\t}\n\t\treturn trace\n\t}\n\n\t// Limit is 3. Batch 1: 2 spans (under limit). Batch 2: 2 spans (partial copy, hits limit).\n\t// Batch 3: 2 spans (already at limit, ignored).\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tif !yield([]ptrace.Traces{createBatch(1, 2)}, nil) {\n\t\t\treturn\n\t\t}\n\t\tif !yield([]ptrace.Traces{createBatch(1, 2)}, nil) {\n\t\t\treturn\n\t\t}\n\t\tyield([]ptrace.Traces{createBatch(1, 2)}, nil)\n\t}\n\n\tvar result []ptrace.Traces\n\tAggregateTracesWithLimit(tracesSeq, 3)(func(trace ptrace.Traces, _ error) bool {\n\t\tresult = append(result, trace)\n\t\treturn true\n\t})\n\n\trequire.Len(t, result, 1)\n\tassert.Equal(t, 3, result[0].SpanCount())\n\n\tfirstSpan := result[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\twarnings := GetWarnings(firstSpan)\n\tassert.Len(t, warnings, 1, \"should have exactly one truncation warning, not one per extra batch\")\n\tassert.Contains(t, warnings[0], fmt.Sprintf(\"trace has more than %d spans\", 3))\n}\n\n// TestAggregateTracesWithLimit_ExactLimitThenOverflow specifically tests the scenario\n// where the first batch fills the trace to exactly maxSize (no warning yet), and a\n// subsequent batch then causes the first overflow and must trigger the truncation warning.\nfunc TestAggregateTracesWithLimit_ExactLimitThenOverflow(t *testing.T) {\n\tcreateBatch := func(traceID byte, spanCount int) ptrace.Traces {\n\t\ttrace := ptrace.NewTraces()\n\t\tspans := trace.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\t\tfor i := 0; i < spanCount; i++ {\n\t\t\tspan := spans.AppendEmpty()\n\t\t\tspan.SetTraceID(pcommon.TraceID([16]byte{traceID}))\n\t\t}\n\t\treturn trace\n\t}\n\n\t// Batch 1 has exactly maxSize spans — fits without truncation, no warning added yet.\n\t// Batch 2 has 1 more span — must be dropped AND must trigger the warning.\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tif !yield([]ptrace.Traces{createBatch(1, 3)}, nil) {\n\t\t\treturn\n\t\t}\n\t\tyield([]ptrace.Traces{createBatch(1, 1)}, nil)\n\t}\n\n\tvar result []ptrace.Traces\n\tAggregateTracesWithLimit(tracesSeq, 3)(func(trace ptrace.Traces, _ error) bool {\n\t\tresult = append(result, trace)\n\t\treturn true\n\t})\n\n\trequire.Len(t, result, 1)\n\tassert.Equal(t, 3, result[0].SpanCount())\n\n\tfirstSpan := result[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\twarnings := GetWarnings(firstSpan)\n\tassert.Len(t, warnings, 1, \"overflow after exact-limit batch must produce exactly one truncation warning\")\n\tassert.Contains(t, warnings[0], fmt.Sprintf(\"trace has more than %d spans\", 3))\n}\n\nfunc TestMarkAndCheckTruncated(t *testing.T) {\n\ttrace := ptrace.NewTraces()\n\tfirstSpan := trace.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tassert.Empty(t, GetWarnings(firstSpan))\n\tmarkTraceTruncated(trace, 10)\n\t// Now should have truncation warning\n\twarnings := GetWarnings(firstSpan)\n\tassert.NotEmpty(t, warnings)\n\tassert.Contains(t, warnings[0], \"trace has more than 10 spans\")\n}\n\nfunc TestAggregateTraces_HandlesEmptyTraces(t *testing.T) {\n\temptyTrace := ptrace.NewTraces() // No resource spans\n\n\ttraceWithNoSpans := ptrace.NewTraces()\n\ttraceWithNoSpans.ResourceSpans().AppendEmpty() // Has resource spans but no scope spans\n\n\ttraceWithNoSpans2 := ptrace.NewTraces()\n\trs := traceWithNoSpans2.ResourceSpans().AppendEmpty()\n\trs.ScopeSpans().AppendEmpty() // Has scope spans but no spans\n\n\ttrace1 := ptrace.NewTraces()\n\trs1 := trace1.ResourceSpans().AppendEmpty()\n\tss1 := rs1.ScopeSpans().AppendEmpty()\n\tspan1 := ss1.Spans().AppendEmpty()\n\tspan1.SetTraceID(pcommon.TraceID([16]byte{1}))\n\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tyield([]ptrace.Traces{emptyTrace, traceWithNoSpans, traceWithNoSpans2, trace1}, nil)\n\t}\n\n\tvar result []ptrace.Traces\n\tAggregateTraces(tracesSeq)(func(trace ptrace.Traces, _ error) bool {\n\t\tresult = append(result, trace)\n\t\treturn true\n\t})\n\n\trequire.Len(t, result, 1)\n\trequire.Equal(t, trace1, result[0])\n}\n\nfunc TestAggregateTraces_DoesNotYieldAfterConsumerStops(t *testing.T) {\n\t// This test demonstrates why the `cont` variable is needed in AggregateTraces.\n\t// Without it, the function would violate the iterator protocol by calling yield\n\t// after the consumer has returned false.\n\t//\n\t// Setup: Create two separate traces with different IDs that will be yielded\n\t// from separate batches. The consumer will stop after the first trace.\n\ttrace1 := ptrace.NewTraces()\n\tresource1 := trace1.ResourceSpans().AppendEmpty()\n\tscope1 := resource1.ScopeSpans().AppendEmpty()\n\tspan1 := scope1.Spans().AppendEmpty()\n\tspan1.SetTraceID(pcommon.TraceID([16]byte{1}))\n\tspan1.SetName(\"span1\")\n\n\ttrace2 := ptrace.NewTraces()\n\tresource2 := trace2.ResourceSpans().AppendEmpty()\n\tscope2 := resource2.ScopeSpans().AppendEmpty()\n\tspan2 := scope2.Spans().AppendEmpty()\n\tspan2.SetTraceID(pcommon.TraceID([16]byte{2}))\n\tspan2.SetName(\"span2\")\n\n\t// Yield traces in separate batches - this ensures the final yield happens\n\t// after the iterator completes, which is where the bug would manifest.\n\ttracesSeq := func(yield func([]ptrace.Traces, error) bool) {\n\t\tif !yield([]ptrace.Traces{trace1}, nil) {\n\t\t\treturn\n\t\t}\n\t\tyield([]ptrace.Traces{trace2}, nil)\n\t}\n\n\tvar yieldCount int\n\taggregatedSeq := AggregateTraces(tracesSeq)\n\n\t// Consumer stops after first yield\n\taggregatedSeq(func(_ ptrace.Traces, _ error) bool {\n\t\tyieldCount++\n\t\treturn false // Stop iteration after first trace\n\t})\n\n\t// Without the `cont` variable, this would panic with:\n\t// \"runtime error: range function continued iteration after function for loop body returned false\"\n\t// The cont variable prevents the final yield (line 48-50 in aggregator.go) from\n\t// being called after the consumer has already returned false.\n\trequire.Equal(t, 1, yieldCount, \"yield should only be called once since consumer returned false\")\n}\n"
  },
  {
    "path": "internal/jptrace/attributes.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\npackage jptrace\n\nimport (\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n)\n\nconst (\n\t// WarningsAttribute is the name of the span attribute where we can\n\t// store various warnings produced from transformations,\n\t// such as inbound sanitizers and outbound adjusters.\n\t// The value type of the attribute is a string slice.\n\tWarningsAttribute = \"@jaeger@warnings\"\n\t// FormatAttribute is a key for span attribute that records the original\n\t// wire format in which the span was received by Jaeger,\n\t// e.g. proto, thrift, json.\n\tFormatAttribute = \"@jaeger@format\"\n)\n\nfunc PcommonMapToPlainMap(attributes pcommon.Map) map[string]string {\n\tmapAttributes := make(map[string]string)\n\tattributes.Range(func(k string, v pcommon.Value) bool {\n\t\tmapAttributes[k] = v.AsString()\n\t\treturn true\n\t})\n\treturn mapAttributes\n}\n\nfunc PlainMapToPcommonMap(attributesMap map[string]string) pcommon.Map {\n\tattributes := pcommon.NewMap()\n\tfor k, v := range attributesMap {\n\t\tattributes.PutStr(k, v)\n\t}\n\treturn attributes\n}\n"
  },
  {
    "path": "internal/jptrace/attributes_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n)\n\nfunc TestPcommonMapToPlainMap(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tattributes pcommon.Map\n\t\texpected   map[string]string\n\t}{\n\t\t{\n\t\t\tname:       \"empty attributes\",\n\t\t\tattributes: pcommon.NewMap(),\n\t\t\texpected:   map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname: \"single attribute\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tm.PutStr(\"key1\", \"value1\")\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: map[string]string{\"key1\": \"value1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple attributes\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tm.PutStr(\"key1\", \"value1\")\n\t\t\t\tm.PutStr(\"key2\", \"value2\")\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: map[string]string{\"key1\": \"value1\", \"key2\": \"value2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"non-string attributes\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tm.PutInt(\"key1\", 1)\n\t\t\t\tm.PutDouble(\"key2\", 3.14)\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: map[string]string{\"key1\": \"1\", \"key2\": \"3.14\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tresult := PcommonMapToPlainMap(test.attributes)\n\t\t\trequire.Equal(t, test.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestPlainMapToPcommonMap(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\texpected map[string]string\n\t}{\n\t\t{\n\t\t\tname:     \"empty map\",\n\t\t\texpected: map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname:     \"single attribute\",\n\t\t\texpected: map[string]string{\"key1\": \"value1\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"multiple attributes\",\n\t\t\texpected: map[string]string{\"key1\": \"value1\", \"key2\": \"value2\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tresult := PlainMapToPcommonMap(test.expected)\n\t\t\trequire.Equal(t, test.expected, PcommonMapToPlainMap(result))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/emptyservicename.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nconst (\n\temptyServiceName     = \"empty-service-name\"\n\tserviceNameWrongType = \"service-name-wrong-type\"\n\tmissingServiceName   = \"missing-service-name\"\n)\n\n// NewEmptyServiceNameSanitizer returns a sanitizer function that replaces\n// empty and missing service names with placeholder strings.\nfunc NewEmptyServiceNameSanitizer() Func {\n\treturn sanitizeEmptyServiceName\n}\n\nfunc sanitizeEmptyServiceName(traces ptrace.Traces) ptrace.Traces {\n\tneedsModification := false\n\tfor _, resourceSpan := range traces.ResourceSpans().All() {\n\t\tattributes := resourceSpan.Resource().Attributes()\n\t\tserviceName, ok := attributes.Get(string(otelsemconv.ServiceNameKey))\n\t\tswitch {\n\t\tcase !ok:\n\t\t\tneedsModification = true\n\t\tcase serviceName.Type() != pcommon.ValueTypeStr:\n\t\t\tneedsModification = true\n\t\tcase serviceName.Str() == \"\":\n\t\t\tneedsModification = true\n\t\tdefault:\n\t\t\t// Service name is valid, no modification needed\n\t\t}\n\t\tif needsModification {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !needsModification {\n\t\treturn traces\n\t}\n\n\tvar workingTraces ptrace.Traces\n\n\tif traces.IsReadOnly() {\n\t\tworkingTraces = ptrace.NewTraces()\n\t\ttraces.CopyTo(workingTraces)\n\t} else {\n\t\tworkingTraces = traces\n\t}\n\n\tfor _, resourceSpan := range workingTraces.ResourceSpans().All() {\n\t\tattributes := resourceSpan.Resource().Attributes()\n\t\tserviceName, ok := attributes.Get(string(otelsemconv.ServiceNameKey))\n\t\tswitch {\n\t\tcase !ok:\n\t\t\tattributes.PutStr(string(otelsemconv.ServiceNameKey), missingServiceName)\n\t\tcase serviceName.Type() != pcommon.ValueTypeStr:\n\t\t\tattributes.PutStr(string(otelsemconv.ServiceNameKey), serviceNameWrongType)\n\t\tcase serviceName.Str() == \"\":\n\t\t\tattributes.PutStr(string(otelsemconv.ServiceNameKey), emptyServiceName)\n\t\tdefault:\n\t\t\t// Service name is valid, no action needed\n\t\t}\n\t}\n\n\treturn workingTraces\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/emptyservicename_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestEmptyServiceNameSanitizer_SubstitutesCorrectlyForStrings(t *testing.T) {\n\temptyServiceName := \"\"\n\tnonEmptyServiceName := \"hello\"\n\ttests := []struct {\n\t\tname                string\n\t\tserviceName         *string\n\t\texpectedServiceName string\n\t}{\n\t\t{\n\t\t\tname:                \"no service name\",\n\t\t\texpectedServiceName: \"missing-service-name\",\n\t\t},\n\t\t{\n\t\t\tname:                \"empty service name\",\n\t\t\tserviceName:         &emptyServiceName,\n\t\t\texpectedServiceName: \"empty-service-name\",\n\t\t},\n\t\t{\n\t\t\tname:                \"non-empty service name\",\n\t\t\tserviceName:         &nonEmptyServiceName,\n\t\t\texpectedServiceName: \"hello\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\tattributes := traces.\n\t\t\t\tResourceSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tResource().\n\t\t\t\tAttributes()\n\t\t\tif test.serviceName != nil {\n\t\t\t\tattributes.PutStr(\"service.name\", *test.serviceName)\n\t\t\t}\n\t\t\tsanitizer := NewEmptyServiceNameSanitizer()\n\t\t\tsanitized := sanitizer(traces)\n\t\t\tserviceName, ok := sanitized.\n\t\t\t\tResourceSpans().\n\t\t\t\tAt(0).\n\t\t\t\tResource().\n\t\t\t\tAttributes().\n\t\t\t\tGet(\"service.name\")\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, test.expectedServiceName, serviceName.Str())\n\t\t})\n\t}\n}\n\nfunc TestEmptyServiceNameSanitizer_SubstitutesCorrectlyForNonStringType(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\ttraces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes().\n\t\tPutInt(\"service.name\", 1)\n\tsanitizer := NewEmptyServiceNameSanitizer()\n\tsanitized := sanitizer(traces)\n\tserviceName, ok := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"service.name\")\n\trequire.True(t, ok)\n\trequire.Equal(t, \"service-name-wrong-type\", serviceName.Str())\n}\n\nfunc TestEmptyServiceNameSanitizer_DefaultCases(t *testing.T) {\n\tvalidServiceName := \"valid-service\"\n\n\ttraces := ptrace.NewTraces()\n\tattributes := traces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes()\n\tattributes.PutStr(\"service.name\", validServiceName)\n\n\tsanitizer := NewEmptyServiceNameSanitizer()\n\tsanitized := sanitizer(traces)\n\n\tserviceName, ok := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"service.name\")\n\trequire.True(t, ok)\n\trequire.Equal(t, validServiceName, serviceName.Str())\n\n\tattributes2 := traces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes()\n\tattributes2.PutStr(\"service.name\", \"\")\n\n\tsanitized = sanitizer(traces)\n\n\tserviceName, ok = sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"service.name\")\n\trequire.True(t, ok)\n\trequire.Equal(t, validServiceName, serviceName.Str())\n\n\tserviceName2, ok := sanitized.\n\t\tResourceSpans().\n\t\tAt(1).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"service.name\")\n\trequire.True(t, ok)\n\trequire.Equal(t, \"empty-service-name\", serviceName2.Str())\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/emptyspanname.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nconst (\n\temptySpanName = \"empty-span-name\"\n)\n\n// NewEmptySpanNameSanitizer returns a sanitizer function that replaces\n// empty span names with a placeholder string.\nfunc NewEmptySpanNameSanitizer() Func {\n\treturn sanitizeEmptySpanName\n}\n\nfunc sanitizeEmptySpanName(traces ptrace.Traces) ptrace.Traces {\n\tif !tracesNeedSpanNameSanitization(traces) {\n\t\treturn traces\n\t}\n\n\tvar workingTraces ptrace.Traces\n\n\tif traces.IsReadOnly() {\n\t\tworkingTraces = ptrace.NewTraces()\n\t\ttraces.CopyTo(workingTraces)\n\t} else {\n\t\tworkingTraces = traces\n\t}\n\n\tfor _, resourceSpan := range workingTraces.ResourceSpans().All() {\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif span.Name() == \"\" {\n\t\t\t\t\tspan.SetName(emptySpanName)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn workingTraces\n}\n\nfunc tracesNeedSpanNameSanitization(traces ptrace.Traces) bool {\n\tfor _, resourceSpan := range traces.ResourceSpans().All() {\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif span.Name() == \"\" {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/emptyspanname_test.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestEmptySpanNameSanitizer(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tspanName         string\n\t\texpectedSpanName string\n\t}{\n\t\t{\n\t\t\tname:             \"empty span name\",\n\t\t\tspanName:         \"\",\n\t\t\texpectedSpanName: \"empty-span-name\",\n\t\t},\n\t\t{\n\t\t\tname:             \"non-empty span name\",\n\t\t\tspanName:         \"my-operation\",\n\t\t\texpectedSpanName: \"my-operation\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\tspan := traces.\n\t\t\t\tResourceSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tScopeSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tSpans().\n\t\t\t\tAppendEmpty()\n\t\t\tspan.SetName(test.spanName)\n\n\t\t\tsanitizer := NewEmptySpanNameSanitizer()\n\t\t\tsanitized := sanitizer(traces)\n\n\t\t\tactualSpan := sanitized.\n\t\t\t\tResourceSpans().\n\t\t\t\tAt(0).\n\t\t\t\tScopeSpans().\n\t\t\t\tAt(0).\n\t\t\t\tSpans().\n\t\t\t\tAt(0)\n\t\t\trequire.Equal(t, test.expectedSpanName, actualSpan.Name())\n\t\t})\n\t}\n}\n\nfunc TestEmptySpanNameSanitizer_ReadOnly(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspan := traces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tScopeSpans().\n\t\tAppendEmpty().\n\t\tSpans().\n\t\tAppendEmpty()\n\tspan.SetName(\"\")\n\n\ttraces.MarkReadOnly()\n\n\tsanitizer := NewEmptySpanNameSanitizer()\n\tresult := sanitizer(traces)\n\n\t// The original read-only traces should still have an empty span name.\n\tassert.Empty(t, traces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())\n\t// The returned traces should have the placeholder span name.\n\tassert.Equal(t, \"empty-span-name\", result.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())\n}\n\nfunc TestEmptySpanNameSanitizer_NoModificationNeeded(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspan := traces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tScopeSpans().\n\t\tAppendEmpty().\n\t\tSpans().\n\t\tAppendEmpty()\n\tspan.SetName(\"valid-span\")\n\n\ttraces.MarkReadOnly()\n\n\tsanitizer := NewEmptySpanNameSanitizer()\n\tresult := sanitizer(traces)\n\n\tassert.Equal(t, \"valid-span\", result.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Name())\n\tassert.True(t, result.IsReadOnly())\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/negative_duration_santizer.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n\npackage sanitizer\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n)\n\nconst (\n\tminDuration = time.Duration(1)\n)\n\n// NewNegativeDurationSanitizer returns a sanitizer function that performs the\n// following sanitizationson the trace data:\n//   - Checks all spans in the ResourceSpans to ensure that the start timestamp\n//     is less than the end timestamp. If not, it sets the end timestamp to\n//     start timestamp plus a minimum duration of 1 nanosecond.\nfunc NewNegativeDurationSanitizer() Func {\n\treturn sanitizeNegativeDuration\n}\n\nfunc sanitizeNegativeDuration(traces ptrace.Traces) ptrace.Traces {\n\tif !tracesNeedDurationSanitization(traces) {\n\t\treturn traces\n\t}\n\n\tvar workingTraces ptrace.Traces\n\n\tif traces.IsReadOnly() {\n\t\tworkingTraces = ptrace.NewTraces()\n\t\ttraces.CopyTo(workingTraces)\n\t} else {\n\t\tworkingTraces = traces\n\t}\n\n\tfor _, resourceSpan := range workingTraces.ResourceSpans().All() {\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tsanitizeDuration(&span)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn workingTraces\n}\n\nfunc tracesNeedDurationSanitization(traces ptrace.Traces) bool {\n\tfor _, resourceSpan := range traces.ResourceSpans().All() {\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif spanNeedsDurationSanitization(span) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc spanNeedsDurationSanitization(span ptrace.Span) bool {\n\tstart := span.StartTimestamp().AsTime()\n\tend := span.EndTimestamp().AsTime()\n\treturn !start.Before(end)\n}\n\nfunc sanitizeDuration(span *ptrace.Span) {\n\tstart := span.StartTimestamp().AsTime()\n\tend := span.EndTimestamp().AsTime()\n\tif start.Before(end) {\n\t\treturn\n\t}\n\n\tnewEnd := start.Add(minDuration)\n\tjptrace.AddWarnings(\n\t\t*span,\n\t\tfmt.Sprintf(\n\t\t\t\"Negative duration detected, sanitizing end timestamp. Original end timestamp: %s, adjusted to: %s\",\n\t\t\tend.String(), newEnd.String(),\n\t\t),\n\t)\n\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(newEnd))\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/negative_duration_santizer_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n)\n\nfunc TestNegativeDurationSanitizer(t *testing.T) {\n\tnow := time.Date(2025, 5, 25, 0, 0, 0, 0, time.UTC)\n\n\ttests := []struct {\n\t\tname          string\n\t\tstart         time.Time\n\t\tend           time.Time\n\t\tdelta         time.Duration // end - start duration\n\t\texpectWarning bool\n\t}{\n\t\t{\n\t\t\tname:          \"valid duration\",\n\t\t\tstart:         now,\n\t\t\tend:           now.Add(5 * time.Second),\n\t\t\tdelta:         5 * time.Second,\n\t\t\texpectWarning: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"negative duration\",\n\t\t\tstart:         now,\n\t\t\tend:           now.Add(-2 * time.Second),\n\t\t\tdelta:         1 * time.Nanosecond,\n\t\t\texpectWarning: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"zero duration\",\n\t\t\tstart:         now,\n\t\t\tend:           now,\n\t\t\tdelta:         1 * time.Nanosecond,\n\t\t\texpectWarning: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\tspan := traces.\n\t\t\t\tResourceSpans().AppendEmpty().\n\t\t\t\tScopeSpans().AppendEmpty().\n\t\t\t\tSpans().AppendEmpty()\n\n\t\t\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(test.start))\n\t\t\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(test.end))\n\n\t\t\tsanitizer := NewNegativeDurationSanitizer()\n\t\t\tsanitized := sanitizer(traces)\n\n\t\t\tsanitizedSpan := sanitized.\n\t\t\t\tResourceSpans().At(0).\n\t\t\t\tScopeSpans().At(0).\n\t\t\t\tSpans().At(0)\n\n\t\t\tgotStart := sanitizedSpan.StartTimestamp().AsTime()\n\t\t\tgotEnd := sanitizedSpan.EndTimestamp().AsTime()\n\n\t\t\trequire.Equal(t, test.start, gotStart)\n\t\t\trequire.Equal(t, test.start.Add(test.delta), gotEnd)\n\n\t\t\tif test.expectWarning {\n\t\t\t\twarnings := jptrace.GetWarnings(sanitizedSpan)\n\t\t\t\trequire.Equal(\n\t\t\t\t\tt,\n\t\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\t\"Negative duration detected, sanitizing end timestamp. Original end timestamp: %s, adjusted to: %s\",\n\t\t\t\t\t\ttest.end.String(),\n\t\t\t\t\t\tgotEnd.String(),\n\t\t\t\t\t),\n\t\t\t\t\twarnings[0],\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/readonly_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\n// Tests that all sanitizers handle read-only traces correctly\n// This reproduces the panic scenario described in the GitHub issue #7221\nfunc TestSanitizersWithReadOnlyTraces(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tsanitizer Func\n\t\tsetupFunc func(ptrace.Traces)\n\t}{\n\t\t{\n\t\t\tname:      \"EmptyServiceNameSanitizer\",\n\t\t\tsanitizer: NewEmptyServiceNameSanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\t// Create a trace without service.name to trigger sanitization\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"test-span\")\n\t\t\t\t// Intentionally not setting service.name to trigger sanitization\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"EmptySpanNameSanitizer\",\n\t\t\tsanitizer: NewEmptySpanNameSanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\t// Create a trace with an empty span name to trigger sanitization\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"UTF8Sanitizer\",\n\t\t\tsanitizer: NewUTF8Sanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\t// Create a trace with invalid UTF-8 to trigger sanitization\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"test-span\")\n\t\t\t\t// Add an attribute with invalid UTF-8\n\t\t\t\tinvalidUTF8 := string([]byte{0xff, 0xfe, 0xfd})\n\t\t\t\tspan.Attributes().PutStr(\"invalid-utf8\", invalidUTF8)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"NegativeDurationSanitizer\",\n\t\t\tsanitizer: NewNegativeDurationSanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\t// Create a trace with negative duration to trigger sanitization\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"test-span\")\n\t\t\t\t// Set end time before start time to create negative duration\n\t\t\t\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(pcommon.Timestamp(2000).AsTime()))\n\t\t\t\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(pcommon.Timestamp(1000).AsTime()))\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\ttt.setupFunc(traces)\n\n\t\t\t// Mark traces as read-only to simulate multiple exporters scenario\n\t\t\ttraces.MarkReadOnly()\n\n\t\t\tresult := tt.sanitizer(traces)\n\t\t\trequire.NotNil(t, result)\n\n\t\t\tassert.NotSame(t, &traces, &result, \"Expected a copy to be made for read-only traces that need modification\")\n\t\t})\n\t}\n}\n\n// Tests that sanitizers return the original\n// traces when no modification is needed, even if they are read-only\nfunc TestSanitizersWithReadOnlyTracesNoModification(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tsanitizer Func\n\t\tsetupFunc func(ptrace.Traces)\n\t}{\n\t\t{\n\t\t\tname:      \"EmptyServiceNameSanitizer_NoModification\",\n\t\t\tsanitizer: NewEmptyServiceNameSanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\trSpans.Resource().Attributes().PutStr(string(otelsemconv.ServiceNameKey), \"valid-service\")\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"test-span\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"EmptySpanNameSanitizer_NoModification\",\n\t\t\tsanitizer: NewEmptySpanNameSanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"valid-span\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"UTF8Sanitizer_NoModification\",\n\t\t\tsanitizer: NewUTF8Sanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"test-span\")\n\t\t\t\tspan.Attributes().PutStr(\"valid-key\", \"valid-value\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"NegativeDurationSanitizer_NoModification\",\n\t\t\tsanitizer: NewNegativeDurationSanitizer(),\n\t\t\tsetupFunc: func(traces ptrace.Traces) {\n\t\t\t\trSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := sSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetName(\"test-span\")\n\t\t\t\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(pcommon.Timestamp(1000).AsTime()))\n\t\t\t\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(pcommon.Timestamp(2000).AsTime()))\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\ttt.setupFunc(traces)\n\n\t\t\ttraces.MarkReadOnly()\n\n\t\t\tresult := tt.sanitizer(traces)\n\t\t\trequire.NotNil(t, result)\n\n\t\t\tassert.True(t, result.IsReadOnly(), \"Expected the result to remain read-only when no modification is needed\")\n\t\t})\n\t}\n}\n\nfunc TestChainedSanitizerWithReadOnlyTraces(t *testing.T) {\n\t// Create traces that need multiple sanitizations\n\ttraces := ptrace.NewTraces()\n\trSpans := traces.ResourceSpans().AppendEmpty()\n\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\tspan := sSpans.Spans().AppendEmpty()\n\tspan.SetName(\"test-span\")\n\n\t// No service.name (triggers empty service name sanitizer)\n\t// Invalid UTF-8 attribute (triggers UTF8 sanitizer)\n\tinvalidUTF8 := string([]byte{0xff, 0xfe, 0xfd})\n\tspan.Attributes().PutStr(\"invalid-utf8\", invalidUTF8)\n\n\t// Negative duration (triggers negative duration sanitizer)\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(pcommon.Timestamp(2000).AsTime()))\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(pcommon.Timestamp(1000).AsTime()))\n\n\ttraces.MarkReadOnly()\n\n\tchainedSanitizer := Sanitize\n\n\tresult := chainedSanitizer(traces)\n\trequire.NotNil(t, result)\n\n\t// Verify that sanitization occurred by checking the service name was added\n\tresultResourceSpans := result.ResourceSpans()\n\trequire.Positive(t, resultResourceSpans.Len())\n\tserviceName, ok := resultResourceSpans.At(0).Resource().Attributes().Get(string(otelsemconv.ServiceNameKey))\n\trequire.True(t, ok)\n\tassert.Equal(t, \"missing-service-name\", serviceName.Str())\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/sanitizer.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\n// Func is a function that performs enrichment, clean-up, or normalization of trace data.\ntype Func func(traces ptrace.Traces) ptrace.Traces\n\n// Sanitize is a function that applies all sanitizers to the given trace data.\nvar Sanitize = NewChainedSanitizer(NewStandardSanitizers()...)\n\n// NewStandardSanitizers returns a list of all the sanitizers that are used by the\n// storage exporter.\nfunc NewStandardSanitizers() []Func {\n\treturn []Func{\n\t\tNewEmptyServiceNameSanitizer(),\n\t\tNewEmptySpanNameSanitizer(),\n\t\tNewUTF8Sanitizer(),\n\t\tNewNegativeDurationSanitizer(),\n\t}\n}\n\n// NewChainedSanitizer creates a Sanitizer from the variadic list of passed Sanitizers.\n// If the list only has one element, it is returned directly to minimize indirection.\nfunc NewChainedSanitizer(sanitizers ...Func) Func {\n\tif len(sanitizers) == 1 {\n\t\treturn sanitizers[0]\n\t}\n\treturn func(traces ptrace.Traces) ptrace.Traces {\n\t\tfor _, s := range sanitizers {\n\t\t\ttraces = s(traces)\n\t\t}\n\t\treturn traces\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/sanitizer_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestNewStandardSanitizers(t *testing.T) {\n\tsanitizers := NewStandardSanitizers()\n\trequire.Len(t, sanitizers, 4)\n}\n\nfunc TestNewChainedSanitizer(t *testing.T) {\n\tvar s1 Func = func(traces ptrace.Traces) ptrace.Traces {\n\t\ttraces.\n\t\t\tResourceSpans().\n\t\t\tAppendEmpty().\n\t\t\tResource().\n\t\t\tAttributes().\n\t\t\tPutStr(\"hello\", \"world\")\n\t\treturn traces\n\t}\n\tvar s2 Func = func(traces ptrace.Traces) ptrace.Traces {\n\t\ttraces.\n\t\t\tResourceSpans().\n\t\t\tAt(0).\n\t\t\tResource().\n\t\t\tAttributes().\n\t\t\tPutStr(\"hello\", \"goodbye\")\n\t\treturn traces\n\t}\n\tc1 := NewChainedSanitizer(s1)\n\tt1 := c1(ptrace.NewTraces())\n\thello, ok := t1.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"hello\")\n\trequire.True(t, ok)\n\trequire.Equal(t, \"world\", hello.Str())\n\tc2 := NewChainedSanitizer(s1, s2)\n\tt2 := c2(ptrace.NewTraces())\n\thello, ok = t2.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"hello\")\n\trequire.True(t, ok)\n\trequire.Equal(t, \"goodbye\", hello.Str())\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/utf8.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"fmt\"\n\t\"unicode/utf8\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nconst (\n\tinvalidSpanName = \"invalid-span-name\"\n\tinvalidTagKey   = \"invalid-tag-key\"\n)\n\n// NewUTF8Sanitizer returns a sanitizer function that performs the following sanitizations\n// on the trace data:\n//   - Checks all attributes of the span to ensure that the keys are valid UTF-8 strings\n//     and all string typed values are valid UTF-8 strings. If any keys or values are invalid,\n//     they are replaced with a valid UTF-8 string containing debugging data related to the invalidations.\n//   - Explicitly checks that all span names are valid UTF-8 strings. If they are not, they are replaced\n//     with a valid UTF-8 string and debugging information is put into the attributes of the span.\nfunc NewUTF8Sanitizer() Func {\n\treturn sanitizeUTF8\n}\n\nfunc sanitizeUTF8(traces ptrace.Traces) ptrace.Traces {\n\tif !tracesNeedUTF8Sanitization(traces) {\n\t\treturn traces\n\t}\n\n\tvar workingTraces ptrace.Traces\n\n\tif traces.IsReadOnly() {\n\t\tworkingTraces = ptrace.NewTraces()\n\t\ttraces.CopyTo(workingTraces)\n\t} else {\n\t\tworkingTraces = traces\n\t}\n\n\tworkingResourceSpans := workingTraces.ResourceSpans()\n\tfor i := 0; i < workingResourceSpans.Len(); i++ {\n\t\tresourceSpan := workingResourceSpans.At(i)\n\t\tsanitizeAttributes(resourceSpan.Resource().Attributes())\n\n\t\tscopeSpans := resourceSpan.ScopeSpans()\n\t\tfor j := 0; j < scopeSpans.Len(); j++ {\n\t\t\tscopeSpan := scopeSpans.At(j)\n\t\t\tsanitizeAttributes(scopeSpan.Scope().Attributes())\n\n\t\t\tspans := scopeSpan.Spans()\n\n\t\t\tfor k := 0; k < spans.Len(); k++ {\n\t\t\t\tspan := spans.At(k)\n\n\t\t\t\tif !utf8.ValidString(span.Name()) {\n\t\t\t\t\tsanitized := []byte(span.Name())\n\t\t\t\t\tnewVal := span.Attributes().PutEmptyBytes(invalidSpanName)\n\t\t\t\t\tnewVal.Append(sanitized...)\n\t\t\t\t\tspan.SetName(invalidSpanName)\n\t\t\t\t}\n\n\t\t\t\tsanitizeAttributes(span.Attributes())\n\t\t\t}\n\t\t}\n\t}\n\n\treturn workingTraces\n}\n\nfunc tracesNeedUTF8Sanitization(traces ptrace.Traces) bool {\n\tfor _, resourceSpan := range traces.ResourceSpans().All() {\n\t\tif attributesNeedUTF8Sanitization(resourceSpan.Resource().Attributes()) {\n\t\t\treturn true\n\t\t}\n\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tif attributesNeedUTF8Sanitization(scopeSpan.Scope().Attributes()) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif !utf8.ValidString(span.Name()) || attributesNeedUTF8Sanitization(span.Attributes()) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc attributesNeedUTF8Sanitization(attributes pcommon.Map) bool {\n\tneedsSanitization := false\n\tfor k, v := range attributes.All() {\n\t\tif !utf8.ValidString(k) {\n\t\t\tneedsSanitization = true\n\t\t\tbreak\n\t\t}\n\t\tif v.Type() == pcommon.ValueTypeStr && !utf8.ValidString(v.Str()) {\n\t\t\tneedsSanitization = true\n\t\t\tbreak\n\t\t}\n\t}\n\treturn needsSanitization\n}\n\nfunc sanitizeAttributes(attributes pcommon.Map) {\n\t// collect invalid keys during iteration to avoid changing the keys of the map\n\t// while iterating over the map using Range\n\tinvalidKeys := make(map[string]pcommon.Value)\n\n\tattributes.Range(func(k string, v pcommon.Value) bool {\n\t\tif !utf8.ValidString(k) {\n\t\t\tinvalidKeys[k] = v\n\t\t}\n\n\t\tif v.Type() == pcommon.ValueTypeStr && !utf8.ValidString(v.Str()) {\n\t\t\tsanitized := []byte(v.Str())\n\t\t\tnewVal := attributes.PutEmptyBytes(k)\n\t\t\tnewVal.Append(sanitized...)\n\t\t}\n\t\treturn true\n\t})\n\n\ti := 1\n\tfor k, v := range invalidKeys {\n\t\tsanitized := []byte(k + \":\")\n\t\tswitch v.Type() {\n\t\tcase pcommon.ValueTypeBytes:\n\t\t\tsanitized = append(sanitized, v.Bytes().AsRaw()...)\n\t\tdefault:\n\t\t\tsanitized = append(sanitized, []byte(v.AsString())...)\n\t\t}\n\n\t\tnewKey := fmt.Sprintf(\"%s-%d\", invalidTagKey, i)\n\t\tnewVal := attributes.PutEmptyBytes(newKey)\n\t\tnewVal.Append(sanitized...)\n\t\ti++\n\t\tattributes.Remove(k)\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/sanitizer/utf8_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sanitizer\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc invalidUTF8() string {\n\ts, _ := hex.DecodeString(\"fefeffff\")\n\treturn string(s)\n}\n\nfunc getBytesValueFromString(s string) pcommon.Value {\n\tb := pcommon.NewValueBytes()\n\tb.Bytes().Append([]byte(s)...)\n\treturn b\n}\n\nvar utf8EncodingTests = []struct {\n\tname          string\n\tkey           string\n\tvalue         string\n\texpectedKey   string\n\texpectedValue pcommon.Value\n}{\n\t{\n\t\tname:          \"valid key + valid value\",\n\t\tkey:           \"key\",\n\t\tvalue:         \"value\",\n\t\texpectedKey:   \"key\",\n\t\texpectedValue: pcommon.NewValueStr(\"value\"),\n\t},\n\t{\n\t\tname:          \"invalid key + valid value\",\n\t\tkey:           invalidUTF8(),\n\t\tvalue:         \"value\",\n\t\texpectedKey:   \"invalid-tag-key-1\",\n\t\texpectedValue: getBytesValueFromString(invalidUTF8() + \":value\"),\n\t},\n\t{\n\t\tname:          \"valid key + invalid value\",\n\t\tkey:           \"key\",\n\t\tvalue:         invalidUTF8(),\n\t\texpectedKey:   \"key\",\n\t\texpectedValue: getBytesValueFromString(invalidUTF8()),\n\t},\n\t{\n\t\tname:          \"invalid key + invalid value\",\n\t\tkey:           invalidUTF8(),\n\t\tvalue:         invalidUTF8(),\n\t\texpectedKey:   \"invalid-tag-key-1\",\n\t\texpectedValue: getBytesValueFromString(fmt.Sprintf(\"%s:%s\", invalidUTF8(), invalidUTF8())),\n\t},\n}\n\nfunc TestUTF8Sanitizer_SanitizesResourceSpanAttributes(t *testing.T) {\n\ttests := utf8EncodingTests\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\ttraces.\n\t\t\t\tResourceSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tResource().\n\t\t\t\tAttributes().\n\t\t\t\tPutStr(test.key, test.value)\n\t\t\tsanitizer := NewUTF8Sanitizer()\n\t\t\tsanitized := sanitizer(traces)\n\t\t\tvalue, ok := sanitized.\n\t\t\t\tResourceSpans().\n\t\t\t\tAt(0).\n\t\t\t\tResource().\n\t\t\t\tAttributes().\n\t\t\t\tGet(test.expectedKey)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, test.expectedValue, value)\n\t\t})\n\t}\n}\n\nfunc TestUTF8Sanitizer_SanitizesScopeSpanAttributes(t *testing.T) {\n\ttests := utf8EncodingTests\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\ttraces.\n\t\t\t\tResourceSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tScopeSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tScope().\n\t\t\t\tAttributes().\n\t\t\t\tPutStr(test.key, test.value)\n\t\t\tsanitizer := NewUTF8Sanitizer()\n\t\t\tsanitized := sanitizer(traces)\n\t\t\tvalue, ok := sanitized.\n\t\t\t\tResourceSpans().\n\t\t\t\tAt(0).\n\t\t\t\tScopeSpans().\n\t\t\t\tAt(0).\n\t\t\t\tScope().\n\t\t\t\tAttributes().\n\t\t\t\tGet(test.expectedKey)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, test.expectedValue, value)\n\t\t})\n\t}\n}\n\nfunc TestUTF8Sanitizer_SanitizesSpanAttributes(t *testing.T) {\n\ttests := utf8EncodingTests\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\ttraces.\n\t\t\t\tResourceSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tScopeSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tSpans().\n\t\t\t\tAppendEmpty().\n\t\t\t\tAttributes().\n\t\t\t\tPutStr(test.key, test.value)\n\t\t\tsanitizer := NewUTF8Sanitizer()\n\t\t\tsanitized := sanitizer(traces)\n\t\t\tvalue, ok := sanitized.\n\t\t\t\tResourceSpans().\n\t\t\t\tAt(0).\n\t\t\t\tScopeSpans().\n\t\t\t\tAt(0).\n\t\t\t\tSpans().\n\t\t\t\tAt(0).\n\t\t\t\tAttributes().\n\t\t\t\tGet(test.expectedKey)\n\t\t\trequire.True(t, ok)\n\t\t\trequire.Equal(t, test.expectedValue, value)\n\t\t})\n\t}\n}\n\nfunc TestUTF8Sanitizer_SanitizesInvalidSpanName(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\ttraces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tScopeSpans().\n\t\tAppendEmpty().\n\t\tSpans().\n\t\tAppendEmpty().\n\t\tSetName(invalidUTF8())\n\tsanitizer := NewUTF8Sanitizer()\n\tsanitized := sanitizer(traces)\n\tname := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tScopeSpans().\n\t\tAt(0).\n\t\tSpans().\n\t\tAt(0).\n\t\tName()\n\trequire.Equal(t, \"invalid-span-name\", name)\n}\n\nfunc TestUTF8Sanitizer_DoesNotSanitizeValidSpanName(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\ttraces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tScopeSpans().\n\t\tAppendEmpty().\n\t\tSpans().\n\t\tAppendEmpty().\n\t\tSetName(\"name\")\n\tsanitizer := NewUTF8Sanitizer()\n\tsanitized := sanitizer(traces)\n\tname := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tScopeSpans().\n\t\tAt(0).\n\t\tSpans().\n\t\tAt(0).\n\t\tName()\n\trequire.Equal(t, \"name\", name)\n}\n\nfunc TestUTF8Sanitizer_RemovesInvalidKeys(t *testing.T) {\n\tk1 := fmt.Sprintf(\"%s-%d\", invalidUTF8(), 1)\n\tk2 := fmt.Sprintf(\"%s-%d\", invalidUTF8(), 2)\n\n\ttraces := ptrace.NewTraces()\n\tattributes := traces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes()\n\n\tattributes.PutStr(k1, \"v1\")\n\tattributes.PutStr(k2, \"v2\")\n\n\tsanitizer := NewUTF8Sanitizer()\n\tsanitized := sanitizer(traces)\n\t_, ok := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(k1)\n\trequire.False(t, ok)\n\n\tsanitizer = NewUTF8Sanitizer()\n\tsanitized = sanitizer(traces)\n\t_, ok = sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(k2)\n\trequire.False(t, ok)\n}\n\nfunc TestUTF8Sanitizer_DoesNotSanitizeNonStringAttributeValue(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\ttraces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes().\n\t\tPutInt(\"key\", 99)\n\tsanitizer := NewUTF8Sanitizer()\n\tsanitized := sanitizer(traces)\n\tvalue, ok := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"key\")\n\trequire.True(t, ok)\n\trequire.EqualValues(t, 99, value.Int())\n}\n\nfunc TestUTF8Sanitizer_SanitizesNonStringAttributeValueWithInvalidKey(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\ttraces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes().\n\t\tPutInt(invalidUTF8(), 99)\n\tsanitizer := NewUTF8Sanitizer()\n\tsanitized := sanitizer(traces)\n\tvalue, ok := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes().\n\t\tGet(\"invalid-tag-key-1\")\n\trequire.True(t, ok)\n\trequire.Equal(t, getBytesValueFromString(invalidUTF8()+\":99\"), value)\n}\n\nfunc TestUTF8Sanitizer_SanitizesMultipleAttributesWithInvalidKeys(t *testing.T) {\n\tk1 := fmt.Sprintf(\"%s-%d\", invalidUTF8(), 1)\n\tk2 := fmt.Sprintf(\"%s-%d\", invalidUTF8(), 2)\n\ttraces := ptrace.NewTraces()\n\tattributes := traces.\n\t\tResourceSpans().\n\t\tAppendEmpty().\n\t\tResource().\n\t\tAttributes()\n\n\tattributes.PutStr(k1, \"v1\")\n\tattributes.PutStr(k2, \"v2\")\n\n\tsanitizer := NewUTF8Sanitizer()\n\tsanitized := sanitizer(traces)\n\tgot := sanitized.\n\t\tResourceSpans().\n\t\tAt(0).\n\t\tResource().\n\t\tAttributes()\n\trequire.Equal(t, 2, got.Len())\n\n\texpectedValues := []pcommon.Value{\n\t\tgetBytesValueFromString(k1 + \":v1\"),\n\t\tgetBytesValueFromString(k2 + \":v2\"),\n\t}\n\tvalue, ok := got.\n\t\tGet(\"invalid-tag-key-1\")\n\trequire.True(t, ok)\n\trequire.Contains(t, expectedValues, value)\n\tchecked := value\n\n\tvalue, ok = got.\n\t\tGet(\"invalid-tag-key-2\")\n\trequire.True(t, ok)\n\trequire.NotEqual(t, checked, value)\n\trequire.Contains(t, expectedValues, value)\n}\n"
  },
  {
    "path": "internal/jptrace/spaniter.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"iter\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\ntype SpanIterPos struct {\n\tResource      ptrace.ResourceSpans\n\tResourceIndex int\n\tScope         ptrace.ScopeSpans\n\tScopeIndex    int\n}\n\n// SpanIter iterates over all spans in the provided ptrace.Traces and yields each span.\nfunc SpanIter(traces ptrace.Traces) iter.Seq2[SpanIterPos, ptrace.Span] {\n\treturn func(yield func(SpanIterPos, ptrace.Span) bool) {\n\t\tvar pos SpanIterPos\n\t\tfor i := 0; i < traces.ResourceSpans().Len(); i++ {\n\t\t\tresource := traces.ResourceSpans().At(i)\n\t\t\tpos.Resource = resource\n\t\t\tpos.ResourceIndex = i\n\t\t\tfor j := 0; j < resource.ScopeSpans().Len(); j++ {\n\t\t\t\tscope := resource.ScopeSpans().At(j)\n\t\t\t\tpos.Scope = scope\n\t\t\t\tpos.ScopeIndex = j\n\t\t\t\tfor k := 0; k < scope.Spans().Len(); k++ {\n\t\t\t\t\tspan := scope.Spans().At(k)\n\t\t\t\t\tif !yield(pos, span) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc GetTraceID(traces ptrace.Traces) pcommon.TraceID {\n\tfor _, span := range SpanIter(traces) {\n\t\treturn span.TraceID()\n\t}\n\treturn pcommon.NewTraceIDEmpty()\n}\n"
  },
  {
    "path": "internal/jptrace/spaniter_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestSpanIter(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\n\tresource1 := traces.ResourceSpans().AppendEmpty()\n\tscope1 := resource1.ScopeSpans().AppendEmpty()\n\n\tspan1 := scope1.Spans().AppendEmpty()\n\tspan1.SetName(\"span-1\")\n\n\tspan2 := scope1.Spans().AppendEmpty()\n\tspan2.SetName(\"span-2\")\n\n\tresource2 := traces.ResourceSpans().AppendEmpty()\n\tscope2 := resource2.ScopeSpans().AppendEmpty()\n\n\tspan3 := scope2.Spans().AppendEmpty()\n\tspan3.SetName(\"span-3\")\n\n\tscope3 := resource2.ScopeSpans().AppendEmpty()\n\n\tspan4 := scope3.Spans().AppendEmpty()\n\tspan4.SetName(\"span-4\")\n\n\tspanIter := SpanIter(traces)\n\tvar spans []ptrace.Span\n\tvar positions []SpanIterPos\n\tspanIter(func(pos SpanIterPos, span ptrace.Span) bool {\n\t\tspans = append(spans, span)\n\t\tpositions = append(positions, pos)\n\t\treturn true\n\t})\n\n\tassert.Len(t, spans, 4)\n\tassert.Equal(t, \"span-1\", spans[0].Name())\n\tassert.Equal(t, \"span-2\", spans[1].Name())\n\tassert.Equal(t, \"span-3\", spans[2].Name())\n\tassert.Equal(t, \"span-4\", spans[3].Name())\n\n\tassert.Len(t, positions, 4)\n\tassert.Equal(t, 0, positions[0].ResourceIndex)\n\tassert.Equal(t, resource1, positions[0].Resource)\n\tassert.Equal(t, 0, positions[0].ScopeIndex)\n\tassert.Equal(t, scope1, positions[0].Scope)\n\n\tassert.Equal(t, 0, positions[1].ResourceIndex)\n\tassert.Equal(t, resource1, positions[1].Resource)\n\tassert.Equal(t, 0, positions[1].ScopeIndex)\n\tassert.Equal(t, scope1, positions[1].Scope)\n\n\tassert.Equal(t, 1, positions[2].ResourceIndex)\n\tassert.Equal(t, resource2, positions[2].Resource)\n\tassert.Equal(t, 0, positions[2].ScopeIndex)\n\tassert.Equal(t, scope2, positions[2].Scope)\n\n\tassert.Equal(t, 1, positions[3].ResourceIndex)\n\tassert.Equal(t, resource2, positions[3].Resource)\n\tassert.Equal(t, 1, positions[3].ScopeIndex)\n\tassert.Equal(t, scope3, positions[3].Scope)\n}\n\nfunc TestSpanIterStopIteration(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\n\tresource1 := traces.ResourceSpans().AppendEmpty()\n\tscope1 := resource1.ScopeSpans().AppendEmpty()\n\n\tspan1 := scope1.Spans().AppendEmpty()\n\tspan1.SetName(\"span-1\")\n\n\tspan2 := scope1.Spans().AppendEmpty()\n\tspan2.SetName(\"span-2\")\n\n\tspanIter := SpanIter(traces)\n\tvar spans []ptrace.Span\n\tspanIter(func(_ SpanIterPos, span ptrace.Span) bool {\n\t\tspans = append(spans, span)\n\t\treturn false\n\t})\n\n\tassert.Len(t, spans, 1)\n\tassert.Equal(t, \"span-1\", spans[0].Name())\n}\n\nfunc TestGetTraceID(t *testing.T) {\n\tt.Run(\"empty traces returns empty TraceID\", func(t *testing.T) {\n\t\ttraces := ptrace.NewTraces()\n\t\tassert.Equal(t, pcommon.NewTraceIDEmpty(), GetTraceID(traces))\n\t})\n\n\tt.Run(\"returns TraceID of first span\", func(t *testing.T) {\n\t\ttraces := ptrace.NewTraces()\n\t\tspan := traces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\t\ttraceID := pcommon.TraceID([16]byte{1, 2, 3})\n\t\tspan.SetTraceID(traceID)\n\t\tassert.Equal(t, traceID, GetTraceID(traces))\n\t})\n}\n"
  },
  {
    "path": "internal/jptrace/spankind.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"strings\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc StringToSpanKind(sk string) ptrace.SpanKind {\n\tswitch strings.ToLower(sk) {\n\tcase \"internal\":\n\t\treturn ptrace.SpanKindInternal\n\tcase \"server\":\n\t\treturn ptrace.SpanKindServer\n\tcase \"client\":\n\t\treturn ptrace.SpanKindClient\n\tcase \"producer\":\n\t\treturn ptrace.SpanKindProducer\n\tcase \"consumer\":\n\t\treturn ptrace.SpanKindConsumer\n\tdefault:\n\t\treturn ptrace.SpanKindUnspecified\n\t}\n}\n\nfunc SpanKindToString(sk ptrace.SpanKind) string {\n\tif sk == ptrace.SpanKindUnspecified {\n\t\treturn \"\"\n\t}\n\treturn strings.ToLower(sk.String())\n}\n"
  },
  {
    "path": "internal/jptrace/spankind_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestStringToSpanKind(t *testing.T) {\n\ttests := []struct {\n\t\tstr  string\n\t\twant ptrace.SpanKind\n\t}{\n\t\t{\n\t\t\tstr:  \"unspecified\",\n\t\t\twant: ptrace.SpanKindUnspecified,\n\t\t},\n\t\t{\n\t\t\tstr:  \"internal\",\n\t\t\twant: ptrace.SpanKindInternal,\n\t\t},\n\t\t{\n\t\t\tstr:  \"server\",\n\t\t\twant: ptrace.SpanKindServer,\n\t\t},\n\t\t{\n\t\t\tstr:  \"client\",\n\t\t\twant: ptrace.SpanKindClient,\n\t\t},\n\t\t{\n\t\t\tstr:  \"producer\",\n\t\t\twant: ptrace.SpanKindProducer,\n\t\t},\n\t\t{\n\t\t\tstr:  \"consumer\",\n\t\t\twant: ptrace.SpanKindConsumer,\n\t\t},\n\t\t{\n\t\t\tstr:  \"unknown\",\n\t\t\twant: ptrace.SpanKindUnspecified,\n\t\t},\n\t\t{\n\t\t\tstr:  \"\",\n\t\t\twant: ptrace.SpanKindUnspecified,\n\t\t},\n\t\t{\n\t\t\tstr:  \"invalid\",\n\t\t\twant: ptrace.SpanKindUnspecified,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.str, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.want, StringToSpanKind(tt.str))\n\t\t})\n\t}\n}\n\nfunc TestSpanKindToString(t *testing.T) {\n\ttests := []struct {\n\t\tkind ptrace.SpanKind\n\t\twant string\n\t}{\n\t\t{\n\t\t\tkind: ptrace.SpanKindUnspecified,\n\t\t\twant: \"\",\n\t\t},\n\t\t{\n\t\t\tkind: ptrace.SpanKindInternal,\n\t\t\twant: \"internal\",\n\t\t},\n\t\t{\n\t\t\tkind: ptrace.SpanKindServer,\n\t\t\twant: \"server\",\n\t\t},\n\t\t{\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\twant: \"client\",\n\t\t},\n\t\t{\n\t\t\tkind: ptrace.SpanKindProducer,\n\t\t\twant: \"producer\",\n\t\t},\n\t\t{\n\t\t\tkind: ptrace.SpanKindConsumer,\n\t\t\twant: \"consumer\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.want, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.want, SpanKindToString(tt.kind))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/spanmap.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport \"go.opentelemetry.io/collector/pdata/ptrace\"\n\n// SpanMap iterates over all spans in the provided ptrace.Traces and maps each span\n// to a key generated by the provided keyFn function. The resulting map has keys of type K\n// and values of type ptrace.Span.\nfunc SpanMap[K comparable](traces ptrace.Traces, keyFn func(ptrace.Span) K) map[K]ptrace.Span {\n\tspanMap := make(map[K]ptrace.Span)\n\tfor i := 0; i < traces.ResourceSpans().Len(); i++ {\n\t\tresource := traces.ResourceSpans().At(i)\n\t\tfor j := 0; j < resource.ScopeSpans().Len(); j++ {\n\t\t\tscope := resource.ScopeSpans().At(j)\n\t\t\tfor k := 0; k < scope.Spans().Len(); k++ {\n\t\t\t\tspan := scope.Spans().At(k)\n\t\t\t\tspanMap[keyFn(span)] = span\n\t\t\t}\n\t\t}\n\t}\n\treturn spanMap\n}\n"
  },
  {
    "path": "internal/jptrace/spanmap_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestSpanMap(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\trs := traces.ResourceSpans().AppendEmpty()\n\tss := rs.ScopeSpans().AppendEmpty()\n\tspan1 := ss.Spans().AppendEmpty()\n\tspan1.SetName(\"span1\")\n\tspan2 := ss.Spans().AppendEmpty()\n\tspan2.SetName(\"span2\")\n\n\tkeyFn := func(span ptrace.Span) string {\n\t\treturn span.Name()\n\t}\n\n\tspanMap := SpanMap(traces, keyFn)\n\n\texpectedMap := map[string]ptrace.Span{\n\t\t\"span1\": span1,\n\t\t\"span2\": span2,\n\t}\n\tassert.Equal(t, expectedMap, spanMap)\n}\n"
  },
  {
    "path": "internal/jptrace/statuscode.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport \"go.opentelemetry.io/collector/pdata/ptrace\"\n\nfunc StringToStatusCode(sc string) ptrace.StatusCode {\n\tswitch sc {\n\tcase \"Ok\":\n\t\treturn ptrace.StatusCodeOk\n\tcase \"Error\":\n\t\treturn ptrace.StatusCodeError\n\tdefault:\n\t\treturn ptrace.StatusCodeUnset\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/statuscode_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestConvertStatusCode(t *testing.T) {\n\ttests := []struct {\n\t\tstr  string\n\t\twant ptrace.StatusCode\n\t}{\n\t\t{\n\t\t\tstr:  \"Ok\",\n\t\t\twant: ptrace.StatusCodeOk,\n\t\t},\n\t\t{\n\t\t\tstr:  \"Unset\",\n\t\t\twant: ptrace.StatusCodeUnset,\n\t\t},\n\t\t{\n\t\t\tstr:  \"Error\",\n\t\t\twant: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tstr:  \"Unknown\",\n\t\t\twant: ptrace.StatusCodeUnset,\n\t\t},\n\t\t{\n\t\t\tstr:  \"\",\n\t\t\twant: ptrace.StatusCodeUnset,\n\t\t},\n\t\t{\n\t\t\tstr:  \"invalid\",\n\t\t\twant: ptrace.StatusCodeUnset,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.str, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.want, StringToStatusCode(tt.str))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/traces.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/gogo/protobuf/proto\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/gogocodec\"\n)\n\n// TracesData is an alias to ptrace.Traces that supports Gogo marshaling.\n// Our .proto APIs may refer to otlp.TraceData type, but its corresponding\n// protoc-generated struct is internal in OTel Collector, so we substitute\n// it for this TracesData type that implements marshaling methods by\n// delegating to public functions in the OTel Collector's ptrace module.\ntype TracesData ptrace.Traces\n\nvar (\n\t_ gogocodec.CustomType = (*TracesData)(nil)\n\t_ proto.Message        = (*TracesData)(nil)\n)\n\nfunc (td TracesData) ToTraces() ptrace.Traces {\n\treturn ptrace.Traces(td)\n}\n\n// Marshal implements gogocodec.CustomType.\nfunc (td *TracesData) Marshal() ([]byte, error) {\n\treturn new(ptrace.ProtoMarshaler).MarshalTraces(td.ToTraces())\n}\n\n// MarshalTo implements gogocodec.CustomType.\nfunc (td *TracesData) MarshalTo(buf []byte) (n int, err error) {\n\treturn td.MarshalToSizedBuffer(buf)\n}\n\n// MarshalToSizedBuffer is used by Gogo.\nfunc (td *TracesData) MarshalToSizedBuffer(buf []byte) (int, error) {\n\tdata, err := td.Marshal()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn := copy(buf, data)\n\treturn n, nil\n}\n\n// MarshalJSONPB implements gogocodec.CustomType.\nfunc (td *TracesData) MarshalJSONPB(*jsonpb.Marshaler) ([]byte, error) {\n\treturn new(ptrace.JSONMarshaler).MarshalTraces(td.ToTraces())\n}\n\n// UnmarshalJSONPB implements gogocodec.CustomType.\nfunc (td *TracesData) UnmarshalJSONPB(_ *jsonpb.Unmarshaler, data []byte) error {\n\tt, err := new(ptrace.JSONUnmarshaler).UnmarshalTraces(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*td = TracesData(t)\n\treturn nil\n}\n\n// Size implements gogocodec.CustomType.\nfunc (td *TracesData) Size() int {\n\treturn new(ptrace.ProtoMarshaler).TracesSize(td.ToTraces())\n}\n\n// Unmarshal implements gogocodec.CustomType.\nfunc (td *TracesData) Unmarshal(data []byte) error {\n\tt, err := new(ptrace.ProtoUnmarshaler).UnmarshalTraces(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*td = TracesData(t)\n\treturn nil\n}\n\n// ProtoMessage implements proto.Message.\nfunc (*TracesData) ProtoMessage() {\n\t// nothing to do here\n}\n\n// Reset implements proto.Message.\nfunc (td *TracesData) Reset() {\n\t*td = TracesData(ptrace.NewTraces())\n}\n\n// String implements proto.Message.\nfunc (*TracesData) String() string {\n\treturn \"*TracesData\"\n}\n"
  },
  {
    "path": "internal/jptrace/traces_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestTracesData(t *testing.T) {\n\ttd := TracesData(ptrace.NewTraces())\n\n\t// Test ToTraces\n\tassert.Equal(t, ptrace.Traces(td), td.ToTraces())\n\n\t// Test Marshal\n\t_, err := td.Marshal()\n\trequire.NoError(t, err)\n\n\t// Test MarshalTo\n\t_, err = td.MarshalTo(make([]byte, td.Size()))\n\trequire.NoError(t, err)\n\n\t// Test MarshalJSONPB\n\t_, err = td.MarshalJSONPB(nil)\n\trequire.NoError(t, err)\n\n\t// Test UnmarshalJSONPB\n\terr = td.UnmarshalJSONPB(nil, []byte(`{\"resourceSpans\":[]}`))\n\trequire.NoError(t, err)\n\n\terr = td.UnmarshalJSONPB(nil, []byte(`{\"resourceSpans\":123}`))\n\trequire.Error(t, err)\n\n\t// Test Size\n\tassert.Equal(t, 0, td.Size())\n\n\t// Test Unmarshal\n\terr = td.Unmarshal([]byte{})\n\trequire.NoError(t, err)\n\terr = td.Unmarshal([]byte{1})\n\trequire.Error(t, err)\n\n\t// Test ProtoMessage\n\ttd.ProtoMessage()\n\n\t// Test Reset\n\ttd.Reset()\n\tassert.Equal(t, TracesData(ptrace.NewTraces()), td)\n\n\t// Test String\n\tassert.Equal(t, \"*TracesData\", td.String())\n}\n"
  },
  {
    "path": "internal/jptrace/valuetype.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"strings\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n)\n\nfunc StringToValueType(vt string) pcommon.ValueType {\n\tswitch strings.ToLower(vt) {\n\tcase \"bool\":\n\t\treturn pcommon.ValueTypeBool\n\tcase \"double\":\n\t\treturn pcommon.ValueTypeDouble\n\tcase \"int\":\n\t\treturn pcommon.ValueTypeInt\n\tcase \"str\":\n\t\treturn pcommon.ValueTypeStr\n\tcase \"bytes\":\n\t\treturn pcommon.ValueTypeBytes\n\tcase \"map\":\n\t\treturn pcommon.ValueTypeMap\n\tcase \"slice\":\n\t\treturn pcommon.ValueTypeSlice\n\tdefault:\n\t\treturn pcommon.ValueTypeEmpty\n\t}\n}\n\nfunc ValueTypeToString(vt pcommon.ValueType) string {\n\tif vt == pcommon.ValueTypeEmpty {\n\t\treturn \"\"\n\t}\n\treturn strings.ToLower(vt.String())\n}\n"
  },
  {
    "path": "internal/jptrace/valuetype_test.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n)\n\nfunc TestStringToValueType(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected pcommon.ValueType\n\t}{\n\t\t{\n\t\t\tname:     \"bool\",\n\t\t\tinput:    \"bool\",\n\t\t\texpected: pcommon.ValueTypeBool,\n\t\t},\n\t\t{\n\t\t\tname:     \"BOOL uppercase\",\n\t\t\tinput:    \"BOOL\",\n\t\t\texpected: pcommon.ValueTypeBool,\n\t\t},\n\t\t{\n\t\t\tname:     \"double\",\n\t\t\tinput:    \"double\",\n\t\t\texpected: pcommon.ValueTypeDouble,\n\t\t},\n\t\t{\n\t\t\tname:     \"int\",\n\t\t\tinput:    \"int\",\n\t\t\texpected: pcommon.ValueTypeInt,\n\t\t},\n\t\t{\n\t\t\tname:     \"str\",\n\t\t\tinput:    \"str\",\n\t\t\texpected: pcommon.ValueTypeStr,\n\t\t},\n\t\t{\n\t\t\tname:     \"bytes\",\n\t\t\tinput:    \"bytes\",\n\t\t\texpected: pcommon.ValueTypeBytes,\n\t\t},\n\t\t{\n\t\t\tname:     \"map\",\n\t\t\tinput:    \"map\",\n\t\t\texpected: pcommon.ValueTypeMap,\n\t\t},\n\t\t{\n\t\t\tname:     \"slice\",\n\t\t\tinput:    \"slice\",\n\t\t\texpected: pcommon.ValueTypeSlice,\n\t\t},\n\t\t{\n\t\t\tname:     \"unknown string\",\n\t\t\tinput:    \"unknown\",\n\t\t\texpected: pcommon.ValueTypeEmpty,\n\t\t},\n\t\t{\n\t\t\tname:     \"empty string\",\n\t\t\tinput:    \"\",\n\t\t\texpected: pcommon.ValueTypeEmpty,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := StringToValueType(tt.input)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestValueTypeToString(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    pcommon.ValueType\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"ValueTypeBool\",\n\t\t\tinput:    pcommon.ValueTypeBool,\n\t\t\texpected: \"bool\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeDouble\",\n\t\t\tinput:    pcommon.ValueTypeDouble,\n\t\t\texpected: \"double\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeInt\",\n\t\t\tinput:    pcommon.ValueTypeInt,\n\t\t\texpected: \"int\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeStr\",\n\t\t\tinput:    pcommon.ValueTypeStr,\n\t\t\texpected: \"str\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeBytes\",\n\t\t\tinput:    pcommon.ValueTypeBytes,\n\t\t\texpected: \"bytes\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeMap\",\n\t\t\tinput:    pcommon.ValueTypeMap,\n\t\t\texpected: \"map\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeSlice\",\n\t\t\tinput:    pcommon.ValueTypeSlice,\n\t\t\texpected: \"slice\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ValueTypeEmpty\",\n\t\t\tinput:    pcommon.ValueTypeEmpty,\n\t\t\texpected: \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := ValueTypeToString(tt.input)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestStringToValueTypeToString(t *testing.T) {\n\tvalidTypes := []string{\"bool\", \"double\", \"int\", \"str\", \"bytes\", \"map\", \"slice\"}\n\tfor _, vt := range validTypes {\n\t\tt.Run(vt, func(t *testing.T) {\n\t\t\tvalueType := StringToValueType(vt)\n\t\t\tresult := ValueTypeToString(valueType)\n\t\t\tassert.Equal(t, vt, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/jptrace/warning.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc AddWarnings(span ptrace.Span, warnings ...string) {\n\tvar w pcommon.Slice\n\tif currWarnings, ok := span.Attributes().Get(WarningsAttribute); ok {\n\t\tw = currWarnings.Slice()\n\t} else {\n\t\tw = span.Attributes().PutEmptySlice(WarningsAttribute)\n\t}\n\tfor _, warning := range warnings {\n\t\tw.AppendEmpty().SetStr(warning)\n\t}\n}\n\nfunc GetWarnings(span ptrace.Span) []string {\n\tif wa, ok := span.Attributes().Get(WarningsAttribute); ok {\n\t\tswitch wa.Type() {\n\t\tcase pcommon.ValueTypeSlice:\n\t\t\twarnings := []string{}\n\t\t\tws := wa.Slice()\n\t\t\tfor i := 0; i < ws.Len(); i++ {\n\t\t\t\twarnings = append(warnings, ws.At(i).Str())\n\t\t\t}\n\t\t\treturn warnings\n\t\tdefault:\n\t\t\t// fallback for malformed data\n\t\t\treturn []string{wa.AsString()}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/jptrace/warning_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jptrace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestAddWarning(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\texisting []string\n\t\tnewWarn  string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:     \"add to nil warnings\",\n\t\t\texisting: nil,\n\t\t\tnewWarn:  \"new warning\",\n\t\t\texpected: []string{\"new warning\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"add to empty warnings\",\n\t\t\texisting: []string{},\n\t\t\tnewWarn:  \"new warning\",\n\t\t\texpected: []string{\"new warning\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"add to existing warnings\",\n\t\t\texisting: []string{\"existing warning 1\", \"existing warning 2\"},\n\t\t\tnewWarn:  \"new warning\",\n\t\t\texpected: []string{\"existing warning 1\", \"existing warning 2\", \"new warning\"},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tspan := ptrace.NewSpan()\n\t\t\tattrs := span.Attributes()\n\t\t\tif test.existing != nil {\n\t\t\t\twarnings := attrs.PutEmptySlice(WarningsAttribute)\n\t\t\t\tfor _, warn := range test.existing {\n\t\t\t\t\twarnings.AppendEmpty().SetStr(warn)\n\t\t\t\t}\n\t\t\t}\n\t\t\tAddWarnings(span, test.newWarn)\n\t\t\twarnings, ok := attrs.Get(WarningsAttribute)\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, len(test.expected), warnings.Slice().Len())\n\t\t\tfor i, expectedWarn := range test.expected {\n\t\t\t\tassert.Equal(t, expectedWarn, warnings.Slice().At(i).Str())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAddWarning_MultipleWarnings(t *testing.T) {\n\tspan := ptrace.NewSpan()\n\tAddWarnings(span, \"warning-1\", \"warning-2\")\n\twarnings, ok := span.Attributes().Get(WarningsAttribute)\n\trequire.True(t, ok)\n\trequire.Equal(t, \"warning-1\", warnings.Slice().At(0).Str())\n\trequire.Equal(t, \"warning-2\", warnings.Slice().At(1).Str())\n}\n\nfunc TestGetWarnings(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\texisting []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:     \"get from nil warnings\",\n\t\t\texisting: nil,\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tname:     \"get from empty warnings\",\n\t\t\texisting: []string{},\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname:     \"get from existing warnings\",\n\t\t\texisting: []string{\"existing warning 1\", \"existing warning 2\"},\n\t\t\texpected: []string{\"existing warning 1\", \"existing warning 2\"},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tspan := ptrace.NewSpan()\n\t\t\tattrs := span.Attributes()\n\t\t\tif test.existing != nil {\n\t\t\t\twarnings := attrs.PutEmptySlice(WarningsAttribute)\n\t\t\t\tfor _, warn := range test.existing {\n\t\t\t\t\twarnings.AppendEmpty().SetStr(warn)\n\t\t\t\t}\n\t\t\t}\n\t\t\tactual := GetWarnings(span)\n\t\t\tassert.Equal(t, test.expected, actual)\n\t\t})\n\t}\n}\n\nfunc TestGetWarnings_EmptySpan(t *testing.T) {\n\tspan := ptrace.NewSpan()\n\tspan.Attributes().PutStr(WarningsAttribute, \"warning-1\")\n\tactual := GetWarnings(span)\n\tassert.Equal(t, []string{\"warning-1\"}, actual)\n}\n"
  },
  {
    "path": "internal/jtracer/jtracer.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jtracer\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"go.opentelemetry.io/contrib/samplers/jaegerremote\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace\"\n\t\"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\"\n\t\"go.opentelemetry.io/otel/propagation\"\n\t\"go.opentelemetry.io/otel/sdk/resource\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nvar once sync.Once\n\nfunc NewProvider(ctx context.Context, serviceName string) (trace.TracerProvider, func(ctx context.Context) error, error) {\n\treturn newProviderHelper(ctx, serviceName, initOTEL)\n}\n\nfunc newProviderHelper(\n\tctx context.Context,\n\tserviceName string,\n\ttracerProvider func(ctx context.Context, svc string) (*sdktrace.TracerProvider, func(), error),\n) (trace.TracerProvider, func(ctx context.Context) error, error) {\n\tprovider, closeSampler, err := tracerProvider(ctx, serviceName)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcloser := func(ctx context.Context) error {\n\t\tif closeSampler != nil {\n\t\t\tcloseSampler()\n\t\t}\n\t\treturn provider.Shutdown(ctx)\n\t}\n\treturn provider, closer, nil\n}\n\n// initOTEL initializes OTEL Tracer\nfunc initOTEL(ctx context.Context, svc string) (*sdktrace.TracerProvider, func(), error) {\n\treturn initHelper(ctx, svc, otelExporter, otelResource)\n}\n\nfunc initHelper(\n\tctx context.Context,\n\tsvc string,\n\totelExporter func(_ context.Context) (sdktrace.SpanExporter, error),\n\totelResource func(_ context.Context, _ /* svc */ string) (*resource.Resource, error),\n) (*sdktrace.TracerProvider, func(), error) {\n\tres, err := otelResource(ctx, svc)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ttraceExporter, err := otelExporter(ctx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// Register the trace exporter with a TracerProvider, using a batch\n\t// span processor to aggregate spans before export.\n\tbsp := sdktrace.NewBatchSpanProcessor(traceExporter)\n\n\topts := []sdktrace.TracerProviderOption{\n\t\tsdktrace.WithSpanProcessor(bsp),\n\t\tsdktrace.WithResource(res),\n\t}\n\n\tvar closeSampler func()\n\tif strings.ToLower(os.Getenv(\"OTEL_TRACES_SAMPLER\")) == \"jaeger_remote\" {\n\t\ts := jaegerremote.New(svc)\n\t\topts = append(opts, sdktrace.WithSampler(s))\n\t\tcloseSampler = s.Close\n\t}\n\n\ttracerProvider := sdktrace.NewTracerProvider(opts...)\n\n\tonce.Do(func() {\n\t\totel.SetTextMapPropagator(\n\t\t\tpropagation.NewCompositeTextMapPropagator(\n\t\t\t\tpropagation.TraceContext{},\n\t\t\t\tpropagation.Baggage{},\n\t\t\t))\n\t})\n\n\totel.SetTracerProvider(tracerProvider)\n\n\treturn tracerProvider, closeSampler, nil\n}\n\nfunc otelResource(ctx context.Context, svc string) (*resource.Resource, error) {\n\treturn resource.New(\n\t\tctx,\n\t\tresource.WithSchemaURL(otelsemconv.SchemaURL),\n\t\tresource.WithAttributes(otelsemconv.ServiceNameAttribute(svc)),\n\t\tresource.WithTelemetrySDK(),\n\t\tresource.WithHost(),\n\t\tresource.WithOSType(),\n\t\tresource.WithFromEnv(),\n\t)\n}\n\nfunc defaultGRPCOptions() []otlptracegrpc.Option {\n\tvar options []otlptracegrpc.Option\n\tif !strings.HasPrefix(os.Getenv(\"OTEL_EXPORTER_OTLP_ENDPOINT\"), \"https://\") && strings.ToLower(os.Getenv(\"OTEL_EXPORTER_OTLP_INSECURE\")) != \"false\" {\n\t\toptions = append(options, otlptracegrpc.WithInsecure())\n\t}\n\treturn options\n}\n\nfunc otelExporter(ctx context.Context) (sdktrace.SpanExporter, error) {\n\tclient := otlptracegrpc.NewClient(\n\t\tdefaultGRPCOptions()...,\n\t)\n\treturn otlptrace.New(ctx, client)\n}\n"
  },
  {
    "path": "internal/jtracer/jtracer_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage jtracer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/otel/sdk/resource\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestNewProvider(t *testing.T) {\n\tp, c, err := NewProvider(t.Context(), \"serviceName\")\n\trequire.NoError(t, err)\n\trequire.NotNil(t, p, \"Expected OTEL not to be nil\")\n\trequire.NotNil(t, c, \"Expected closer not to be nil\")\n\tc(t.Context())\n}\n\nfunc TestNewHelperProviderError(t *testing.T) {\n\tfakeErr := errors.New(\"fakeProviderError\")\n\t_, _, err := newProviderHelper(\n\t\tt.Context(),\n\t\t\"svc\",\n\t\tfunc(_ context.Context, _ /* svc */ string) (*sdktrace.TracerProvider, func(), error) {\n\t\t\treturn nil, nil, fakeErr\n\t\t})\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, fakeErr.Error())\n}\n\nfunc TestInitHelperExporterError(t *testing.T) {\n\tfakeErr := errors.New(\"fakeExporterError\")\n\t_, _, err := initHelper(\n\t\tcontext.Background(),\n\t\t\"svc\",\n\t\tfunc(_ context.Context) (sdktrace.SpanExporter, error) {\n\t\t\treturn nil, fakeErr\n\t\t},\n\t\tfunc(_ context.Context, _ /* svc */ string) (*resource.Resource, error) {\n\t\t\treturn nil, nil\n\t\t},\n\t)\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, fakeErr.Error())\n}\n\nfunc TestInitHelperResourceError(t *testing.T) {\n\tfakeErr := errors.New(\"fakeResourceError\")\n\ttp, _, err := initHelper(\n\t\tcontext.Background(),\n\t\t\"svc\",\n\t\totelExporter,\n\t\tfunc(_ context.Context, _ /* svc */ string) (*resource.Resource, error) {\n\t\t\treturn nil, fakeErr\n\t\t},\n\t)\n\trequire.Error(t, err)\n\trequire.Nil(t, tp)\n\trequire.EqualError(t, err, fakeErr.Error())\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/leaderelection/leader_election.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage leaderelection\n\nimport (\n\t\"io\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\tdl \"github.com/jaegertracing/jaeger/internal/distributedlock\"\n)\n\nconst (\n\tacquireLockErrMsg = \"Failed to acquire lock\"\n)\n\n// ElectionParticipant partakes in leader election to become leader.\ntype ElectionParticipant interface {\n\tio.Closer\n\n\tIsLeader() bool\n\tStart() error\n}\n\n// DistributedElectionParticipant implements ElectionParticipant on top of a distributed lock.\ntype DistributedElectionParticipant struct {\n\tElectionParticipantOptions\n\tlock         dl.Lock\n\tisLeader     atomic.Bool\n\tresourceName string\n\tcloseChan    chan struct{}\n\twg           sync.WaitGroup\n}\n\n// ElectionParticipantOptions control behavior of the election participant. TODO func applyDefaults(), parameter error checking, etc.\ntype ElectionParticipantOptions struct {\n\tLeaderLeaseRefreshInterval   time.Duration\n\tFollowerLeaseRefreshInterval time.Duration\n\tLogger                       *zap.Logger\n}\n\n// NewElectionParticipant returns a ElectionParticipant which attempts to become leader.\nfunc NewElectionParticipant(lock dl.Lock, resourceName string, options ElectionParticipantOptions) *DistributedElectionParticipant {\n\treturn &DistributedElectionParticipant{\n\t\tElectionParticipantOptions: options,\n\t\tlock:                       lock,\n\t\tresourceName:               resourceName,\n\t\tcloseChan:                  make(chan struct{}),\n\t}\n}\n\n// Start runs a background thread which attempts to acquire the leader lock.\nfunc (p *DistributedElectionParticipant) Start() error {\n\tp.wg.Add(1)\n\tgo p.runAcquireLockLoop()\n\treturn nil\n}\n\n// Close implements io.Closer.\nfunc (p *DistributedElectionParticipant) Close() error {\n\tclose(p.closeChan)\n\tp.wg.Wait()\n\treturn nil\n}\n\n// IsLeader returns true if this process is the leader.\nfunc (p *DistributedElectionParticipant) IsLeader() bool {\n\treturn p.isLeader.Load()\n}\n\n// runAcquireLockLoop attempts to acquire the leader lock. If it succeeds, it will attempt to retain it,\n// otherwise it sleeps and attempts to gain the lock again.\nfunc (p *DistributedElectionParticipant) runAcquireLockLoop() {\n\tdefer p.wg.Done()\n\tticker := time.NewTicker(p.acquireLock())\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tticker.Stop()\n\t\t\tticker = time.NewTicker(p.acquireLock())\n\t\tcase <-p.closeChan:\n\t\t\tticker.Stop()\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// acquireLock attempts to acquire the lock and returns the interval to sleep before the next retry.\nfunc (p *DistributedElectionParticipant) acquireLock() time.Duration {\n\tif acquiredLeaderLock, err := p.lock.Acquire(p.resourceName, p.FollowerLeaseRefreshInterval); err == nil {\n\t\tp.setLeader(acquiredLeaderLock)\n\t} else {\n\t\tp.Logger.Error(acquireLockErrMsg, zap.Error(err))\n\t}\n\tif p.IsLeader() {\n\t\t// If this process holds the leader lock, retry with a shorter cadence\n\t\t// to retain the leader lease.\n\t\treturn p.LeaderLeaseRefreshInterval\n\t}\n\t// If this process failed to acquire the leader lock, retry with a longer cadence\n\treturn p.FollowerLeaseRefreshInterval\n}\n\nfunc (p *DistributedElectionParticipant) setLeader(isLeader bool) {\n\tp.isLeader.Store(isLeader)\n}\n"
  },
  {
    "path": "internal/leaderelection/leader_election_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage leaderelection\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tlmocks \"github.com/jaegertracing/jaeger/internal/distributedlock/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nvar errTestLock = errors.New(\"lock error\")\n\nvar _ io.Closer = new(DistributedElectionParticipant)\n\nfunc TestAcquireLock(t *testing.T) {\n\tconst (\n\t\tleaderInterval   = time.Millisecond\n\t\tfollowerInterval = 5 * time.Millisecond\n\t)\n\ttests := []struct {\n\t\tisLeader         bool\n\t\tacquiredLock     bool\n\t\terr              error\n\t\texpectedInterval time.Duration\n\t\texpectedError    bool\n\t}{\n\t\t{isLeader: true, acquiredLock: true, err: nil, expectedInterval: leaderInterval, expectedError: false},\n\t\t{isLeader: true, acquiredLock: false, err: errTestLock, expectedInterval: leaderInterval, expectedError: true},\n\t\t{isLeader: true, acquiredLock: false, err: nil, expectedInterval: followerInterval, expectedError: false},\n\t\t{isLeader: false, acquiredLock: false, err: nil, expectedInterval: followerInterval, expectedError: false},\n\t\t{isLeader: false, acquiredLock: false, err: errTestLock, expectedInterval: followerInterval, expectedError: true},\n\t\t{isLeader: false, acquiredLock: true, err: nil, expectedInterval: leaderInterval, expectedError: false},\n\t}\n\n\tfor i, test := range tests {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tlogger, logBuffer := testutils.NewLogger()\n\t\t\tmockLock := &lmocks.Lock{}\n\t\t\tmockLock.On(\"Acquire\", \"sampling_lock\", followerInterval).Return(test.acquiredLock, test.err)\n\n\t\t\tp := &DistributedElectionParticipant{\n\t\t\t\tElectionParticipantOptions: ElectionParticipantOptions{\n\t\t\t\t\tLeaderLeaseRefreshInterval:   leaderInterval,\n\t\t\t\t\tFollowerLeaseRefreshInterval: followerInterval,\n\t\t\t\t\tLogger:                       logger,\n\t\t\t\t},\n\t\t\t\tlock:         mockLock,\n\t\t\t\tresourceName: \"sampling_lock\",\n\t\t\t}\n\n\t\t\tp.setLeader(test.isLeader)\n\t\t\tassert.Equal(t, test.expectedInterval, p.acquireLock())\n\t\t\tmatch, errMsg := testutils.LogMatcher(1, acquireLockErrMsg, logBuffer.Lines())\n\t\t\tassert.Equal(t, test.expectedError, match, errMsg)\n\t\t})\n\t}\n}\n\nfunc TestRunAcquireLockLoopFollowerOnly(t *testing.T) {\n\tlogger, logBuffer := testutils.NewLogger()\n\tmockLock := &lmocks.Lock{}\n\tmockLock.On(\"Acquire\", \"sampling_lock\", time.Duration(5*time.Millisecond)).Return(false, errTestLock)\n\n\tp := NewElectionParticipant(mockLock, \"sampling_lock\", ElectionParticipantOptions{\n\t\tLeaderLeaseRefreshInterval:   time.Millisecond,\n\t\tFollowerLeaseRefreshInterval: 5 * time.Millisecond,\n\t\tLogger:                       logger,\n\t},\n\t)\n\n\tdefer func() {\n\t\trequire.NoError(t, p.Close())\n\t}()\n\tgo p.Start()\n\n\texpectedErrorMsg := \"Failed to acquire lock\"\n\tfor range 1000 {\n\t\t// match logs specific to acquireLockErrMsg.\n\t\tif match, _ := testutils.LogMatcher(2, expectedErrorMsg, logBuffer.Lines()); match {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Millisecond)\n\t}\n\tmatch, errMsg := testutils.LogMatcher(2, expectedErrorMsg, logBuffer.Lines())\n\tassert.True(t, match, errMsg)\n\tassert.False(t, p.IsLeader())\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/leaderelection/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewElectionParticipant creates a new instance of ElectionParticipant. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewElectionParticipant(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ElectionParticipant {\n\tmock := &ElectionParticipant{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ElectionParticipant is an autogenerated mock type for the ElectionParticipant type\ntype ElectionParticipant struct {\n\tmock.Mock\n}\n\ntype ElectionParticipant_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ElectionParticipant) EXPECT() *ElectionParticipant_Expecter {\n\treturn &ElectionParticipant_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type ElectionParticipant\nfunc (_mock *ElectionParticipant) Close() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Close\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ElectionParticipant_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype ElectionParticipant_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\nfunc (_e *ElectionParticipant_Expecter) Close() *ElectionParticipant_Close_Call {\n\treturn &ElectionParticipant_Close_Call{Call: _e.mock.On(\"Close\")}\n}\n\nfunc (_c *ElectionParticipant_Close_Call) Run(run func()) *ElectionParticipant_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ElectionParticipant_Close_Call) Return(err error) *ElectionParticipant_Close_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ElectionParticipant_Close_Call) RunAndReturn(run func() error) *ElectionParticipant_Close_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// IsLeader provides a mock function for the type ElectionParticipant\nfunc (_mock *ElectionParticipant) IsLeader() bool {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for IsLeader\")\n\t}\n\n\tvar r0 bool\n\tif returnFunc, ok := ret.Get(0).(func() bool); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\treturn r0\n}\n\n// ElectionParticipant_IsLeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsLeader'\ntype ElectionParticipant_IsLeader_Call struct {\n\t*mock.Call\n}\n\n// IsLeader is a helper method to define mock.On call\nfunc (_e *ElectionParticipant_Expecter) IsLeader() *ElectionParticipant_IsLeader_Call {\n\treturn &ElectionParticipant_IsLeader_Call{Call: _e.mock.On(\"IsLeader\")}\n}\n\nfunc (_c *ElectionParticipant_IsLeader_Call) Run(run func()) *ElectionParticipant_IsLeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ElectionParticipant_IsLeader_Call) Return(b bool) *ElectionParticipant_IsLeader_Call {\n\t_c.Call.Return(b)\n\treturn _c\n}\n\nfunc (_c *ElectionParticipant_IsLeader_Call) RunAndReturn(run func() bool) *ElectionParticipant_IsLeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Start provides a mock function for the type ElectionParticipant\nfunc (_mock *ElectionParticipant) Start() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Start\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ElectionParticipant_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'\ntype ElectionParticipant_Start_Call struct {\n\t*mock.Call\n}\n\n// Start is a helper method to define mock.On call\nfunc (_e *ElectionParticipant_Expecter) Start() *ElectionParticipant_Start_Call {\n\treturn &ElectionParticipant_Start_Call{Call: _e.mock.On(\"Start\")}\n}\n\nfunc (_c *ElectionParticipant_Start_Call) Run(run func()) *ElectionParticipant_Start_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ElectionParticipant_Start_Call) Return(err error) *ElectionParticipant_Start_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ElectionParticipant_Start_Call) RunAndReturn(run func() error) *ElectionParticipant_Start_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/metrics/benchmark/benchmark_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage benchmark_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/stretchr/testify/require\"\n\tpromexporter \"go.opentelemetry.io/otel/exporters/prometheus\"\n\t\"go.opentelemetry.io/otel/sdk/metric\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics/otelmetrics\"\n\tprom \"github.com/jaegertracing/jaeger/internal/metrics/prometheus\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n\nfunc setupPrometheusFactory() metrics.Factory {\n\treg := prometheus.NewRegistry()\n\treturn prom.New(prom.WithRegisterer(reg))\n}\n\nfunc setupOTELFactory(b *testing.B) metrics.Factory {\n\tregistry := prometheus.NewRegistry()\n\texporter, err := promexporter.New(promexporter.WithRegisterer(registry))\n\trequire.NoError(b, err)\n\tmeterProvider := metric.NewMeterProvider(\n\t\tmetric.WithReader(exporter),\n\t)\n\treturn otelmetrics.NewFactory(meterProvider)\n}\n\nfunc benchmarkCounter(b *testing.B, factory metrics.Factory) {\n\tcounter := factory.Counter(metrics.Options{\n\t\tName: \"test_counter\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\n\tfor i := 0; i < b.N; i++ {\n\t\tcounter.Inc(1)\n\t}\n}\n\nfunc benchmarkGauge(b *testing.B, factory metrics.Factory) {\n\tgauge := factory.Gauge(metrics.Options{\n\t\tName: \"test_gauge\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\n\tfor i := 0; i < b.N; i++ {\n\t\tgauge.Update(1)\n\t}\n}\n\nfunc benchmarkTimer(b *testing.B, factory metrics.Factory) {\n\ttimer := factory.Timer(metrics.TimerOptions{\n\t\tName: \"test_timer\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\n\tfor i := 0; i < b.N; i++ {\n\t\ttimer.Record(100)\n\t}\n}\n\nfunc benchmarkHistogram(b *testing.B, factory metrics.Factory) {\n\thistogram := factory.Histogram(metrics.HistogramOptions{\n\t\tName: \"test_histogram\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\n\tfor i := 0; i < b.N; i++ {\n\t\thistogram.Record(1.0)\n\t}\n}\n\nfunc BenchmarkPrometheusCounter(b *testing.B) {\n\tbenchmarkCounter(b, setupPrometheusFactory())\n}\n\nfunc BenchmarkOTELCounter(b *testing.B) {\n\tbenchmarkCounter(b, setupOTELFactory(b))\n}\n\nfunc BenchmarkPrometheusGauge(b *testing.B) {\n\tbenchmarkGauge(b, setupPrometheusFactory())\n}\n\nfunc BenchmarkOTELGauge(b *testing.B) {\n\tbenchmarkGauge(b, setupOTELFactory(b))\n}\n\nfunc BenchmarkPrometheusTimer(b *testing.B) {\n\tbenchmarkTimer(b, setupPrometheusFactory())\n}\n\nfunc BenchmarkOTELTimer(b *testing.B) {\n\tbenchmarkTimer(b, setupOTELFactory(b))\n}\n\nfunc BenchmarkPrometheusHistogram(b *testing.B) {\n\tbenchmarkHistogram(b, setupPrometheusFactory())\n}\n\nfunc BenchmarkOTELHistogram(b *testing.B) {\n\tbenchmarkHistogram(b, setupOTELFactory(b))\n}\n"
  },
  {
    "path": "internal/metrics/counter.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\n// Counter tracks the number of times an event has occurred\ntype Counter interface {\n\t// Inc adds the given value to the counter.\n\tInc(int64)\n}\n\n// NullCounter counter that does nothing\nvar NullCounter Counter = nullCounter{}\n\ntype nullCounter struct{}\n\nfunc (nullCounter) Inc(int64) {}\n"
  },
  {
    "path": "internal/metrics/factory.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\nimport (\n\t\"time\"\n)\n\n// NSOptions defines the name and tags map associated with a factory namespace\ntype NSOptions struct {\n\tName string\n\tTags map[string]string\n}\n\n// Options defines the information associated with a metric\ntype Options struct {\n\tName string\n\tTags map[string]string\n\tHelp string\n}\n\n// TimerOptions defines the information associated with a metric\ntype TimerOptions struct {\n\tName    string\n\tTags    map[string]string\n\tHelp    string\n\tBuckets []time.Duration\n}\n\n// HistogramOptions defines the information associated with a metric\ntype HistogramOptions struct {\n\tName    string\n\tTags    map[string]string\n\tHelp    string\n\tBuckets []float64\n}\n\n// Factory creates new metrics\ntype Factory interface {\n\tCounter(metric Options) Counter\n\tTimer(metric TimerOptions) Timer\n\tGauge(metric Options) Gauge\n\tHistogram(metric HistogramOptions) Histogram\n\n\t// Namespace returns a nested metrics factory.\n\tNamespace(scope NSOptions) Factory\n}\n\n// NullFactory is a metrics factory that returns NullCounter, NullTimer, and NullGauge.\nvar NullFactory Factory = nullFactory{}\n\ntype nullFactory struct{}\n\nfunc (nullFactory) Counter(Options) Counter {\n\treturn NullCounter\n}\n\nfunc (nullFactory) Timer(TimerOptions) Timer {\n\treturn NullTimer\n}\n\nfunc (nullFactory) Gauge(Options) Gauge {\n\treturn NullGauge\n}\n\nfunc (nullFactory) Histogram(HistogramOptions) Histogram {\n\treturn NullHistogram\n}\nfunc (nullFactory) Namespace(NSOptions /* scope */) Factory { return NullFactory }\n"
  },
  {
    "path": "internal/metrics/gauge.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\n// Gauge returns instantaneous measurements of something as an int64 value\ntype Gauge interface {\n\t// Update the gauge to the value passed in.\n\tUpdate(int64)\n}\n\n// NullGauge gauge that does nothing\nvar NullGauge Gauge = nullGauge{}\n\ntype nullGauge struct{}\n\nfunc (nullGauge) Update(int64) {}\n"
  },
  {
    "path": "internal/metrics/histogram.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\n// Histogram that keeps track of a distribution of values.\ntype Histogram interface {\n\t// Records the value passed in.\n\tRecord(float64)\n}\n\n// NullHistogram that does nothing\nvar NullHistogram Histogram = nullHistogram{}\n\ntype nullHistogram struct{}\n\nfunc (nullHistogram) Record(float64) {}\n"
  },
  {
    "path": "internal/metrics/metrics.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// MustInit initializes the passed in metrics and initializes its fields using the passed in factory.\n//\n// It uses reflection to initialize a struct containing metrics fields\n// by assigning new Counter/Gauge/Timer values with the metric name retrieved\n// from the `metric` tag and stats tags retrieved from the `tags` tag.\n//\n// Note: all fields of the struct must be exported, have a `metric` tag, and be\n// of type Counter or Gauge or Timer.\n//\n// Errors during Init lead to a panic.\nfunc MustInit(metrics any, factory Factory, globalTags map[string]string) {\n\tif err := Init(metrics, factory, globalTags); err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n\n// Init does the same as MustInit, but returns an error instead of\n// panicking.\nfunc Init(m any, factory Factory, globalTags map[string]string) error {\n\t// Allow user to opt out of reporting metrics by passing in nil.\n\tif factory == nil {\n\t\tfactory = NullFactory\n\t}\n\n\tcounterPtrType := reflect.TypeFor[Counter]()\n\tgaugePtrType := reflect.TypeFor[Gauge]()\n\ttimerPtrType := reflect.TypeFor[Timer]()\n\thistogramPtrType := reflect.TypeFor[Histogram]()\n\n\tv := reflect.ValueOf(m).Elem()\n\tt := v.Type()\n\tfor i := 0; i < t.NumField(); i++ {\n\t\ttags := make(map[string]string)\n\t\tmaps.Copy(tags, globalTags)\n\t\tvar buckets []float64\n\t\tfield := t.Field(i)\n\t\tmetric := field.Tag.Get(\"metric\")\n\t\tif metric == \"\" {\n\t\t\treturn fmt.Errorf(\"Field %s is missing a tag 'metric'\", field.Name)\n\t\t}\n\t\tif tagString := field.Tag.Get(\"tags\"); tagString != \"\" {\n\t\t\tfor tagPair := range strings.SplitSeq(tagString, \",\") {\n\t\t\t\ttag := strings.Split(tagPair, \"=\")\n\t\t\t\tif len(tag) != 2 {\n\t\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\t\"Field [%s]: Tag [%s] is not of the form key=value in 'tags' string [%s]\",\n\t\t\t\t\t\tfield.Name, tagPair, tagString)\n\t\t\t\t}\n\t\t\t\ttags[tag[0]] = tag[1]\n\t\t\t}\n\t\t}\n\t\tif bucketString := field.Tag.Get(\"buckets\"); bucketString != \"\" {\n\t\t\tswitch {\n\t\t\tcase field.Type.AssignableTo(timerPtrType):\n\t\t\t\t// TODO: Parse timer duration buckets\n\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\"Field [%s]: Buckets are not currently initialized for timer metrics\",\n\t\t\t\t\tfield.Name)\n\t\t\tcase field.Type.AssignableTo(histogramPtrType):\n\t\t\t\tbucketValues := strings.Split(bucketString, \",\")\n\t\t\t\tfor _, bucket := range bucketValues {\n\t\t\t\t\tb, err := strconv.ParseFloat(bucket, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\t\t\"Field [%s]: Bucket [%s] could not be converted to float64 in 'buckets' string [%s]\",\n\t\t\t\t\t\t\tfield.Name, bucket, bucketString)\n\t\t\t\t\t}\n\t\t\t\t\tbuckets = append(buckets, b)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\"Field [%s]: Buckets should only be defined for Timer and Histogram metric types\",\n\t\t\t\t\tfield.Name)\n\t\t\t}\n\t\t}\n\t\thelp := field.Tag.Get(\"help\")\n\t\tvar obj any\n\t\tswitch {\n\t\tcase field.Type.AssignableTo(counterPtrType):\n\t\t\tobj = factory.Counter(Options{\n\t\t\t\tName: metric,\n\t\t\t\tTags: tags,\n\t\t\t\tHelp: help,\n\t\t\t})\n\t\tcase field.Type.AssignableTo(gaugePtrType):\n\t\t\tobj = factory.Gauge(Options{\n\t\t\t\tName: metric,\n\t\t\t\tTags: tags,\n\t\t\t\tHelp: help,\n\t\t\t})\n\t\tcase field.Type.AssignableTo(timerPtrType):\n\t\t\t// TODO: Add buckets once parsed (see TODO above)\n\t\t\tobj = factory.Timer(TimerOptions{\n\t\t\t\tName: metric,\n\t\t\t\tTags: tags,\n\t\t\t\tHelp: help,\n\t\t\t})\n\t\tcase field.Type.AssignableTo(histogramPtrType):\n\t\t\tobj = factory.Histogram(HistogramOptions{\n\t\t\t\tName:    metric,\n\t\t\t\tTags:    tags,\n\t\t\t\tHelp:    help,\n\t\t\t\tBuckets: buckets,\n\t\t\t})\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"Field %s is not a pointer to timer, gauge, or counter\",\n\t\t\t\tfield.Name)\n\t\t}\n\t\tv.Field(i).Set(reflect.ValueOf(obj))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/metrics/metrics_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n// Must use separate test package to break import cycle.\npackage metrics_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestInitMetrics(t *testing.T) {\n\ttestMetrics := struct {\n\t\tGauge     metrics.Gauge     `metric:\"gauge\" tags:\"1=one,2=two\"`\n\t\tCounter   metrics.Counter   `metric:\"counter\"`\n\t\tTimer     metrics.Timer     `metric:\"timer\"`\n\t\tHistogram metrics.Histogram `metric:\"histogram\" buckets:\"20,40,60,80\"`\n\t}{}\n\n\tf := metricstest.NewFactory(0)\n\tdefer f.Stop()\n\n\tglobalTags := map[string]string{\"key\": \"value\"}\n\n\terr := metrics.Init(&testMetrics, f, globalTags)\n\trequire.NoError(t, err)\n\n\ttestMetrics.Gauge.Update(10)\n\ttestMetrics.Counter.Inc(5)\n\ttestMetrics.Timer.Record(time.Duration(time.Second * 35))\n\ttestMetrics.Histogram.Record(42)\n\n\t// wait for metrics\n\tfor range 1000 {\n\t\tc, _ := f.Snapshot()\n\t\tif _, ok := c[\"counter\"]; ok {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(1 * time.Millisecond)\n\t}\n\n\tc, g := f.Snapshot()\n\n\tassert.EqualValues(t, 5, c[\"counter|key=value\"])\n\tassert.EqualValues(t, 10, g[\"gauge|1=one|2=two|key=value\"])\n\tassert.EqualValues(t, 35000, g[\"timer|key=value.P50\"])\n\tassert.EqualValues(t, 42, g[\"histogram|key=value.P50\"])\n\n\tstopwatch := metrics.StartStopwatch(testMetrics.Timer)\n\tstopwatch.Stop()\n\tassert.Positive(t, stopwatch.ElapsedTime())\n}\n\nvar (\n\tnoMetricTag = struct {\n\t\tNoMetricTag metrics.Counter\n\t}{}\n\n\tbadTags = struct {\n\t\tBadTags metrics.Counter `metric:\"counter\" tags:\"1=one,noValue\"`\n\t}{}\n\n\tinvalidMetricType = struct {\n\t\tInvalidMetricType int64 `metric:\"counter\"`\n\t}{}\n\n\tbadHistogramBucket = struct {\n\t\tBadHistogramBucket metrics.Histogram `metric:\"histogram\" buckets:\"1,2,a,4\"`\n\t}{}\n\n\tbadTimerBucket = struct {\n\t\tBadTimerBucket metrics.Timer `metric:\"timer\" buckets:\"1\"`\n\t}{}\n\n\tinvalidBuckets = struct {\n\t\tInvalidBuckets metrics.Counter `metric:\"counter\" buckets:\"1\"`\n\t}{}\n)\n\nfunc TestInitMetricsFailures(t *testing.T) {\n\trequire.EqualError(t, metrics.Init(&noMetricTag, nil, nil), \"Field NoMetricTag is missing a tag 'metric'\")\n\n\trequire.EqualError(t, metrics.Init(&badTags, nil, nil),\n\t\t\"Field [BadTags]: Tag [noValue] is not of the form key=value in 'tags' string [1=one,noValue]\")\n\n\trequire.EqualError(t, metrics.Init(&invalidMetricType, nil, nil),\n\t\t\"Field InvalidMetricType is not a pointer to timer, gauge, or counter\")\n\n\trequire.EqualError(t, metrics.Init(&badHistogramBucket, nil, nil),\n\t\t\"Field [BadHistogramBucket]: Bucket [a] could not be converted to float64 in 'buckets' string [1,2,a,4]\")\n\n\trequire.EqualError(t, metrics.Init(&badTimerBucket, nil, nil),\n\t\t\"Field [BadTimerBucket]: Buckets are not currently initialized for timer metrics\")\n\n\trequire.EqualError(t, metrics.Init(&invalidBuckets, nil, nil),\n\t\t\"Field [InvalidBuckets]: Buckets should only be defined for Timer and Histogram metric types\")\n}\n\nfunc TestInitPanic(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Error(\"The code did not panic\")\n\t\t}\n\t}()\n\n\tmetrics.MustInit(&noMetricTag, metrics.NullFactory, nil)\n}\n\nfunc TestNullMetrics(*testing.T) {\n\t// This test is just for cover\n\tmetrics.NullFactory.Timer(metrics.TimerOptions{\n\t\tName: \"name\",\n\t}).Record(0)\n\tmetrics.NullFactory.Counter(metrics.Options{\n\t\tName: \"name\",\n\t}).Inc(0)\n\tmetrics.NullFactory.Gauge(metrics.Options{\n\t\tName: \"name\",\n\t}).Update(0)\n\tmetrics.NullFactory.Histogram(metrics.HistogramOptions{\n\t\tName: \"name\",\n\t}).Record(0)\n\tmetrics.NullFactory.Namespace(metrics.NSOptions{\n\t\tName: \"name\",\n\t}).Gauge(metrics.Options{\n\t\tName: \"name2\",\n\t}).Update(0)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/metrics/metricsbuilder/builder.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricsbuilder\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"net/http\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tjprom \"github.com/jaegertracing/jaeger/internal/metrics/prometheus\"\n)\n\nconst (\n\tmetricsBackend        = \"metrics-backend\"\n\tmetricsHTTPRoute      = \"metrics-http-route\"\n\tdefaultMetricsBackend = \"prometheus\"\n\tdefaultMetricsRoute   = \"/metrics\"\n)\n\nvar errUnknownBackend = errors.New(\"unknown metrics backend specified\")\n\n// Builder provides command line options to configure metrics backend used by Jaeger executables.\ntype Builder struct {\n\tBackend   string\n\tHTTPRoute string // endpoint name to expose metrics, e.g. for scraping\n\thandler   http.Handler\n}\n\n// AddFlags adds flags for Builder.\nfunc AddFlags(flags *flag.FlagSet) {\n\tflags.String(\n\t\tmetricsBackend,\n\t\tdefaultMetricsBackend,\n\t\t\"Defines which metrics backend to use for metrics reporting: prometheus or none\")\n\tflags.String(\n\t\tmetricsHTTPRoute,\n\t\tdefaultMetricsRoute,\n\t\t\"Defines the route of HTTP endpoint for metrics backends that support scraping\")\n}\n\n// InitFromViper initializes Builder with properties retrieved from Viper.\nfunc (b *Builder) InitFromViper(v *viper.Viper) *Builder {\n\tb.Backend = v.GetString(metricsBackend)\n\tb.HTTPRoute = v.GetString(metricsHTTPRoute)\n\treturn b\n}\n\n// CreateMetricsFactory creates a metrics factory based on the configured type of the backend.\n// If the metrics backend supports HTTP endpoint for scraping, it is stored in the builder and\n// can be later added by RegisterHandler function.\nfunc (b *Builder) CreateMetricsFactory(namespace string) (metrics.Factory, error) {\n\tif b.Backend == \"prometheus\" {\n\t\tmetricsFactory := jprom.New().Namespace(metrics.NSOptions{Name: namespace, Tags: nil})\n\t\tb.handler = promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{DisableCompression: true})\n\t\treturn metricsFactory, nil\n\t}\n\tif b.Backend == \"none\" || b.Backend == \"\" {\n\t\treturn metrics.NullFactory, nil\n\t}\n\treturn nil, errUnknownBackend\n}\n\n// Handler returns an http.Handler for the metrics endpoint.\nfunc (b *Builder) Handler() http.Handler {\n\treturn b.handler\n}\n"
  },
  {
    "path": "internal/metrics/metricsbuilder/builder_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricsbuilder\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestAddFlags(t *testing.T) {\n\tv := viper.New()\n\tcommand := cobra.Command{}\n\tflags := &flag.FlagSet{}\n\tAddFlags(flags)\n\tcommand.PersistentFlags().AddGoFlagSet(flags)\n\tv.BindPFlags(command.PersistentFlags())\n\n\tcommand.ParseFlags([]string{\n\t\t\"--metrics-backend=foo\",\n\t\t\"--metrics-http-route=bar\",\n\t})\n\n\tb := &Builder{}\n\tb.InitFromViper(v)\n\n\tassert.Equal(t, \"foo\", b.Backend)\n\tassert.Equal(t, \"bar\", b.HTTPRoute)\n}\n\nfunc TestBuilder(t *testing.T) {\n\tassertPromCounter := func() {\n\t\tfamilies, err := prometheus.DefaultGatherer.Gather()\n\t\trequire.NoError(t, err)\n\t\tfor _, mf := range families {\n\t\t\tif mf.GetName() == \"foo_counter_total\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tt.FailNow()\n\t}\n\ttestCases := []struct {\n\t\tbackend string\n\t\troute   string\n\t\terr     error\n\t\thandler bool\n\t\tassert  func()\n\t}{\n\t\t{\n\t\t\tbackend: \"prometheus\",\n\t\t\troute:   \"/\",\n\t\t\thandler: true,\n\t\t\tassert:  assertPromCounter,\n\t\t},\n\t\t{\n\t\t\tbackend: \"none\",\n\t\t\thandler: false,\n\t\t},\n\t\t{\n\t\t\tbackend: \"\",\n\t\t\thandler: false,\n\t\t},\n\t\t{\n\t\t\tbackend: \"invalid\",\n\t\t\terr:     errUnknownBackend,\n\t\t},\n\t}\n\n\tfor i := range testCases {\n\t\ttestCase := testCases[i]\n\t\tb := &Builder{\n\t\t\tBackend:   testCase.backend,\n\t\t\tHTTPRoute: testCase.route,\n\t\t}\n\t\tmf, err := b.CreateMetricsFactory(\"foo\")\n\t\tif testCase.err != nil {\n\t\t\tassert.Equal(t, err, testCase.err)\n\t\t\tcontinue\n\t\t}\n\t\trequire.NotNil(t, mf)\n\t\tmf.Counter(metrics.Options{Name: \"counter\", Tags: nil}).Inc(1)\n\t\tif testCase.assert != nil {\n\t\t\ttestCase.assert()\n\t\t}\n\t\tif testCase.handler {\n\t\t\trequire.NotNil(t, b.Handler())\n\t\t}\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/metrics/otelmetrics/counter.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelmetrics\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel/metric\"\n)\n\ntype otelCounter struct {\n\tcounter  metric.Int64Counter\n\tfixedCtx context.Context\n\toption   metric.AddOption\n}\n\nfunc (c *otelCounter) Inc(value int64) {\n\tc.counter.Add(c.fixedCtx, value, c.option)\n}\n"
  },
  {
    "path": "internal/metrics/otelmetrics/factory.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelmetrics\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"maps\"\n\t\"strings\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/metric\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\ntype otelFactory struct {\n\tmeter      metric.Meter\n\tscope      string\n\tseparator  string\n\tnormalizer *strings.Replacer\n\ttags       map[string]string\n}\n\nfunc NewFactory(meterProvider metric.MeterProvider) metrics.Factory {\n\treturn &otelFactory{\n\t\tmeter:      meterProvider.Meter(\"jaeger-v2\"),\n\t\tseparator:  \".\",\n\t\tnormalizer: strings.NewReplacer(\" \", \"_\", \".\", \"_\", \"-\", \"_\"),\n\t\ttags:       make(map[string]string),\n\t}\n}\n\nfunc (f *otelFactory) Counter(opts metrics.Options) metrics.Counter {\n\tcounter, err := f.meter.Int64Counter(f.subScope(opts.Name))\n\tif err != nil {\n\t\tlog.Printf(\"Error creating OTEL counter: %v\", err)\n\t\treturn metrics.NullCounter\n\t}\n\treturn &otelCounter{\n\t\tcounter:  counter,\n\t\tfixedCtx: context.Background(),\n\t\toption:   attributeSetOption(f.mergeTags(opts.Tags)),\n\t}\n}\n\nfunc (f *otelFactory) Gauge(opts metrics.Options) metrics.Gauge {\n\tname := f.subScope(opts.Name)\n\tgauge, err := f.meter.Int64Gauge(name)\n\tif err != nil {\n\t\tlog.Printf(\"Error creating OTEL gauge: %v\", err)\n\t\treturn metrics.NullGauge\n\t}\n\n\treturn &otelGauge{\n\t\tgauge:    gauge,\n\t\tfixedCtx: context.Background(),\n\t\toption:   attributeSetOption(f.mergeTags(opts.Tags)),\n\t}\n}\n\nfunc (f *otelFactory) Histogram(opts metrics.HistogramOptions) metrics.Histogram {\n\tname := f.subScope(opts.Name)\n\thistogram, err := f.meter.Float64Histogram(name)\n\tif err != nil {\n\t\tlog.Printf(\"Error creating OTEL histogram: %v\", err)\n\t\treturn metrics.NullHistogram\n\t}\n\n\treturn &otelHistogram{\n\t\thistogram: histogram,\n\t\tfixedCtx:  context.Background(),\n\t\toption:    attributeSetOption(f.mergeTags(opts.Tags)),\n\t}\n}\n\nfunc (f *otelFactory) Timer(opts metrics.TimerOptions) metrics.Timer {\n\tname := f.subScope(opts.Name)\n\ttimer, err := f.meter.Float64Histogram(name, metric.WithUnit(\"s\"))\n\tif err != nil {\n\t\tlog.Printf(\"Error creating OTEL timer: %v\", err)\n\t\treturn metrics.NullTimer\n\t}\n\treturn &otelTimer{\n\t\thistogram: timer,\n\t\tfixedCtx:  context.Background(),\n\t\toption:    attributeSetOption(f.mergeTags(opts.Tags)),\n\t}\n}\n\nfunc (f *otelFactory) Namespace(opts metrics.NSOptions) metrics.Factory {\n\treturn &otelFactory{\n\t\tmeter:      f.meter,\n\t\tscope:      f.subScope(opts.Name),\n\t\tseparator:  f.separator,\n\t\tnormalizer: f.normalizer,\n\t\ttags:       f.mergeTags(opts.Tags),\n\t}\n}\n\nfunc (f *otelFactory) subScope(name string) string {\n\tif f.scope == \"\" {\n\t\treturn f.normalize(name)\n\t}\n\tif name == \"\" {\n\t\treturn f.normalize(f.scope)\n\t}\n\treturn f.normalize(f.scope + f.separator + name)\n}\n\nfunc (f *otelFactory) normalize(v string) string {\n\treturn f.normalizer.Replace(v)\n}\n\nfunc (f *otelFactory) mergeTags(tags map[string]string) map[string]string {\n\tmerged := make(map[string]string)\n\tmaps.Copy(merged, f.tags)\n\tmaps.Copy(merged, tags)\n\treturn merged\n}\n\nfunc attributeSetOption(tags map[string]string) metric.MeasurementOption {\n\tattributes := make([]attribute.KeyValue, 0, len(tags))\n\tfor k, v := range tags {\n\t\tattributes = append(attributes, attribute.String(k, v))\n\t}\n\treturn metric.WithAttributes(attributes...)\n}\n"
  },
  {
    "path": "internal/metrics/otelmetrics/factory_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelmetrics_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\tpromreg \"github.com/prometheus/client_golang/prometheus\"\n\tprommodel \"github.com/prometheus/client_model/go\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/otel/exporters/prometheus\"\n\tsdkmetric \"go.opentelemetry.io/otel/sdk/metric\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics/otelmetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n\nfunc newTestFactory(t *testing.T, registry *promreg.Registry) metrics.Factory {\n\texporter, err := prometheus.New(\n\t\tprometheus.WithRegisterer(registry),\n\t\tprometheus.WithoutScopeInfo(),\n\t)\n\trequire.NoError(t, err)\n\tmeterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(exporter))\n\treturn otelmetrics.NewFactory(meterProvider)\n}\n\nfunc findMetric(t *testing.T, registry *promreg.Registry, name string) *prommodel.MetricFamily {\n\tmetricFamilies, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tfor _, mf := range metricFamilies {\n\t\tt.Log(mf.GetName())\n\t\tt.Log(name)\n\t\tif mf.GetName() == name {\n\t\t\treturn mf\n\t\t}\n\t}\n\trequire.Fail(t, \"Expected to find Metric Family\")\n\treturn nil\n}\n\nfunc promLabelsToMap(labels []*prommodel.LabelPair) map[string]string {\n\tlabelMap := make(map[string]string)\n\tfor _, label := range labels {\n\t\tlabelMap[label.GetName()] = label.GetValue()\n\t}\n\treturn labelMap\n}\n\nfunc TestInvalidCounter(t *testing.T) {\n\tfactory := newTestFactory(t, promreg.NewPedanticRegistry())\n\tcounter := factory.Counter(metrics.Options{\n\t\tName: \"invalid*counter%\",\n\t})\n\tassert.Equal(t, counter, metrics.NullCounter, \"Expected NullCounter, got %v\", counter)\n}\n\nfunc TestInvalidGauge(t *testing.T) {\n\tfactory := newTestFactory(t, promreg.NewPedanticRegistry())\n\tgauge := factory.Gauge(metrics.Options{\n\t\tName: \"#invalid>gauge%\",\n\t})\n\tassert.Equal(t, gauge, metrics.NullGauge, \"Expected NullCounter, got %v\", gauge)\n}\n\nfunc TestInvalidHistogram(t *testing.T) {\n\tfactory := newTestFactory(t, promreg.NewPedanticRegistry())\n\thistogram := factory.Histogram(metrics.HistogramOptions{\n\t\tName: \"invalid>histogram?%\",\n\t})\n\tassert.Equal(t, histogram, metrics.NullHistogram, \"Expected NullCounter, got %v\", histogram)\n}\n\nfunc TestInvalidTimer(t *testing.T) {\n\tfactory := newTestFactory(t, promreg.NewPedanticRegistry())\n\ttimer := factory.Timer(metrics.TimerOptions{\n\t\tName: \"invalid*<=timer%\",\n\t})\n\tassert.Equal(t, timer, metrics.NullTimer, \"Expected NullCounter, got %v\", timer)\n}\n\nfunc TestCounter(t *testing.T) {\n\tregistry := promreg.NewPedanticRegistry()\n\tfactory := newTestFactory(t, registry)\n\tcounter := factory.Counter(metrics.Options{\n\t\tName: \"test_counter\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\trequire.NotNil(t, counter)\n\tcounter.Inc(1)\n\tcounter.Inc(1)\n\n\ttestCounter := findMetric(t, registry, \"test_counter_total\")\n\tmetricData := testCounter.GetMetric()\n\tassert.InDelta(t, float64(2), metricData[0].GetCounter().GetValue(), 0.01)\n\texpectedLabels := map[string]string{\n\t\t\"tag1\": \"value1\",\n\t}\n\tassert.Equal(t, expectedLabels, promLabelsToMap(metricData[0].GetLabel()))\n}\n\nfunc TestGauge(t *testing.T) {\n\tregistry := promreg.NewPedanticRegistry()\n\tfactory := newTestFactory(t, registry)\n\tgauge := factory.Gauge(metrics.Options{\n\t\tName: \"test_gauge\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\trequire.NotNil(t, gauge)\n\tgauge.Update(2)\n\n\ttestGauge := findMetric(t, registry, \"test_gauge\")\n\n\tmetricData := testGauge.GetMetric()\n\tassert.InDelta(t, float64(2), metricData[0].GetGauge().GetValue(), 0.01)\n\texpectedLabels := map[string]string{\n\t\t\"tag1\": \"value1\",\n\t}\n\tassert.Equal(t, expectedLabels, promLabelsToMap(metricData[0].GetLabel()))\n}\n\nfunc TestHistogram(t *testing.T) {\n\tregistry := promreg.NewPedanticRegistry()\n\tfactory := newTestFactory(t, registry)\n\thistogram := factory.Histogram(metrics.HistogramOptions{\n\t\tName: \"test_histogram\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\trequire.NotNil(t, histogram)\n\thistogram.Record(1.0)\n\n\ttestHistogram := findMetric(t, registry, \"test_histogram\")\n\n\tmetricData := testHistogram.GetMetric()\n\tassert.InDelta(t, float64(1), metricData[0].GetHistogram().GetSampleSum(), 0.01)\n\texpectedLabels := map[string]string{\n\t\t\"tag1\": \"value1\",\n\t}\n\tassert.Equal(t, expectedLabels, promLabelsToMap(metricData[0].GetLabel()))\n}\n\nfunc TestTimer(t *testing.T) {\n\tregistry := promreg.NewPedanticRegistry()\n\tfactory := newTestFactory(t, registry)\n\ttimer := factory.Timer(metrics.TimerOptions{\n\t\tName: \"test_timer\",\n\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t})\n\trequire.NotNil(t, timer)\n\ttimer.Record(100 * time.Millisecond)\n\n\ttestTimer := findMetric(t, registry, \"test_timer_seconds\")\n\n\tmetricData := testTimer.GetMetric()\n\tassert.InDelta(t, float64(0.1), metricData[0].GetHistogram().GetSampleSum(), 0.01)\n\texpectedLabels := map[string]string{\n\t\t\"tag1\": \"value1\",\n\t}\n\tassert.Equal(t, expectedLabels, promLabelsToMap(metricData[0].GetLabel()))\n}\n\nfunc TestNamespace(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tnsOptions1     metrics.NSOptions\n\t\tnsOptions2     metrics.NSOptions\n\t\texpectedName   string\n\t\texpectedLabels map[string]string\n\t}{\n\t\t{\n\t\t\tname: \"Nested Namespace\",\n\t\t\tnsOptions1: metrics.NSOptions{\n\t\t\t\tName: \"first_namespace\",\n\t\t\t\tTags: map[string]string{\"ns_tag1\": \"ns_value1\"},\n\t\t\t},\n\t\t\tnsOptions2: metrics.NSOptions{\n\t\t\t\tName: \"second_namespace\",\n\t\t\t\tTags: map[string]string{\"ns_tag3\": \"ns_value3\"},\n\t\t\t},\n\t\t\texpectedName: \"first_namespace_second_namespace_test_counter_total\",\n\t\t\texpectedLabels: map[string]string{\n\t\t\t\t\"ns_tag1\": \"ns_value1\",\n\t\t\t\t\"ns_tag3\": \"ns_value3\",\n\t\t\t\t\"tag1\":    \"value1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Single Namespace\",\n\t\t\tnsOptions1: metrics.NSOptions{\n\t\t\t\tName: \"single_namespace\",\n\t\t\t\tTags: map[string]string{\"ns_tag2\": \"ns_value2\"},\n\t\t\t},\n\t\t\tnsOptions2:   metrics.NSOptions{},\n\t\t\texpectedName: \"single_namespace_test_counter_total\",\n\t\t\texpectedLabels: map[string]string{\n\t\t\t\t\"ns_tag2\": \"ns_value2\",\n\t\t\t\t\"tag1\":    \"value1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"Empty Namespace Name\",\n\t\t\tnsOptions1:   metrics.NSOptions{},\n\t\t\tnsOptions2:   metrics.NSOptions{},\n\t\t\texpectedName: \"test_counter_total\",\n\t\t\texpectedLabels: map[string]string{\n\t\t\t\t\"tag1\": \"value1\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tregistry := promreg.NewPedanticRegistry()\n\t\t\tfactory := newTestFactory(t, registry)\n\t\t\tnsFactory1 := factory.Namespace(tc.nsOptions1)\n\t\t\tnsFactory2 := nsFactory1.Namespace(tc.nsOptions2)\n\n\t\t\tcounter := nsFactory2.Counter(metrics.Options{\n\t\t\t\tName: \"test_counter\",\n\t\t\t\tTags: map[string]string{\"tag1\": \"value1\"},\n\t\t\t})\n\t\t\trequire.NotNil(t, counter)\n\t\t\tcounter.Inc(1)\n\n\t\t\ttestCounter := findMetric(t, registry, tc.expectedName)\n\n\t\t\tmetrics := testCounter.GetMetric()\n\t\t\tassert.InDelta(t, float64(1), metrics[0].GetCounter().GetValue(), 0.01)\n\t\t\tassert.Equal(t, tc.expectedLabels, promLabelsToMap(metrics[0].GetLabel()))\n\t\t})\n\t}\n}\n\nfunc TestNormalization(t *testing.T) {\n\tregistry := promreg.NewPedanticRegistry()\n\tfactory := newTestFactory(t, registry)\n\tnormalizedFactory := factory.Namespace(metrics.NSOptions{\n\t\tName: \"My Namespace\",\n\t})\n\n\tgauge := normalizedFactory.Gauge(metrics.Options{\n\t\tName: \"My Gauge\",\n\t})\n\trequire.NotNil(t, gauge)\n\tgauge.Update(1)\n\n\ttestGauge := findMetric(t, registry, \"My_Namespace_My_Gauge\")\n\n\tmetricData := testGauge.GetMetric()\n\tassert.InDelta(t, float64(1), metricData[0].GetGauge().GetValue(), 0.01)\n}\n"
  },
  {
    "path": "internal/metrics/otelmetrics/gauge.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelmetrics\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel/metric\"\n)\n\ntype otelGauge struct {\n\tgauge    metric.Int64Gauge\n\tfixedCtx context.Context\n\toption   metric.RecordOption\n}\n\nfunc (g *otelGauge) Update(value int64) {\n\tg.gauge.Record(g.fixedCtx, value, g.option)\n}\n"
  },
  {
    "path": "internal/metrics/otelmetrics/histogram.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelmetrics\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel/metric\"\n)\n\ntype otelHistogram struct {\n\thistogram metric.Float64Histogram\n\tfixedCtx  context.Context\n\toption    metric.RecordOption\n}\n\nfunc (h *otelHistogram) Record(value float64) {\n\th.histogram.Record(h.fixedCtx, value, h.option)\n}\n"
  },
  {
    "path": "internal/metrics/otelmetrics/timer.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelmetrics\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/metric\"\n)\n\ntype otelTimer struct {\n\thistogram metric.Float64Histogram\n\tfixedCtx  context.Context\n\toption    metric.RecordOption\n}\n\nfunc (t *otelTimer) Record(d time.Duration) {\n\tt.histogram.Record(t.fixedCtx, d.Seconds(), t.option)\n}\n"
  },
  {
    "path": "internal/metrics/package.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package metrics provides an internal abstraction for metrics API,\n// and command line flags for configuring the metrics backend.\npackage metrics\n"
  },
  {
    "path": "internal/metrics/prometheus/cache.go",
    "content": "// Copyright (c) 2017 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\n// vectorCache is used to avoid creating Prometheus vectors with the same set of labels more than once.\ntype vectorCache struct {\n\tregisterer prometheus.Registerer\n\tlock       sync.Mutex\n\tcVecs      map[string]*prometheus.CounterVec\n\tgVecs      map[string]*prometheus.GaugeVec\n\thVecs      map[string]*prometheus.HistogramVec\n}\n\nfunc newVectorCache(registerer prometheus.Registerer) *vectorCache {\n\treturn &vectorCache{\n\t\tregisterer: registerer,\n\t\tcVecs:      make(map[string]*prometheus.CounterVec),\n\t\tgVecs:      make(map[string]*prometheus.GaugeVec),\n\t\thVecs:      make(map[string]*prometheus.HistogramVec),\n\t}\n}\n\nfunc (c *vectorCache) getOrMakeCounterVec(opts prometheus.CounterOpts, labelNames []string) *prometheus.CounterVec {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tcacheKey := c.getCacheKey(opts.Name, labelNames)\n\tcv, cvExists := c.cVecs[cacheKey]\n\tif !cvExists {\n\t\tcv = prometheus.NewCounterVec(opts, labelNames)\n\t\tc.registerer.MustRegister(cv)\n\t\tc.cVecs[cacheKey] = cv\n\t}\n\treturn cv\n}\n\nfunc (c *vectorCache) getOrMakeGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tcacheKey := c.getCacheKey(opts.Name, labelNames)\n\tgv, gvExists := c.gVecs[cacheKey]\n\tif !gvExists {\n\t\tgv = prometheus.NewGaugeVec(opts, labelNames)\n\t\tc.registerer.MustRegister(gv)\n\t\tc.gVecs[cacheKey] = gv\n\t}\n\treturn gv\n}\n\nfunc (c *vectorCache) getOrMakeHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tcacheKey := c.getCacheKey(opts.Name, labelNames)\n\thv, hvExists := c.hVecs[cacheKey]\n\tif !hvExists {\n\t\thv = prometheus.NewHistogramVec(opts, labelNames)\n\t\tc.registerer.MustRegister(hv)\n\t\tc.hVecs[cacheKey] = hv\n\t}\n\treturn hv\n}\n\nfunc (*vectorCache) getCacheKey(name string, labels []string) string {\n\treturn strings.Join(append([]string{name}, labels...), \"||\")\n}\n"
  },
  {
    "path": "internal/metrics/prometheus/factory.go",
    "content": "// Copyright (c) 2017 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus\n\nimport (\n\t\"maps\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\n// Factory implements metrics.Factory backed by Prometheus registry.\ntype Factory struct {\n\tscope      string\n\ttags       map[string]string\n\tcache      *vectorCache\n\tbuckets    []float64\n\tnormalizer *strings.Replacer\n\tseparator  Separator\n}\n\nvar _ metrics.Factory = (*Factory)(nil)\n\ntype options struct {\n\tregisterer prometheus.Registerer\n\tbuckets    []float64\n\tseparator  Separator\n}\n\n// Separator represents the namespace separator to use\ntype Separator rune\n\nconst (\n\t// SeparatorUnderscore uses an underscore as separator\n\tSeparatorUnderscore Separator = '_'\n\n\t// SeparatorColon uses a colon as separator\n\tSeparatorColon = ':'\n)\n\n// Option is a function that sets some option for the Factory constructor.\ntype Option func(*options)\n\n// WithRegisterer returns an option that sets the registerer.\n// If not used we fallback to prometheus.DefaultRegisterer.\nfunc WithRegisterer(registerer prometheus.Registerer) Option {\n\treturn func(opts *options) {\n\t\topts.registerer = registerer\n\t}\n}\n\n// WithBuckets returns an option that sets the default buckets for histogram.\n// If not used, we fallback to default Prometheus buckets.\nfunc WithBuckets(buckets []float64) Option {\n\treturn func(opts *options) {\n\t\topts.buckets = buckets\n\t}\n}\n\n// WithSeparator returns an option that sets the default separator for the namespace\n// If not used, we fallback to underscore.\nfunc WithSeparator(separator Separator) Option {\n\treturn func(opts *options) {\n\t\topts.separator = separator\n\t}\n}\n\nfunc applyOptions(opts []Option) *options {\n\toptions := new(options)\n\tfor _, o := range opts {\n\t\to(options)\n\t}\n\tif options.registerer == nil {\n\t\toptions.registerer = prometheus.DefaultRegisterer\n\t}\n\tif options.separator == '\\x00' {\n\t\toptions.separator = SeparatorUnderscore\n\t}\n\treturn options\n}\n\n// New creates a Factory backed by Prometheus registry.\n// Typically the first argument should be prometheus.DefaultRegisterer.\n//\n// Parameter buckets defines the buckets into which Timer observations are counted.\n// Each element in the slice is the upper inclusive bound of a bucket. The\n// values must be sorted in strictly increasing order. There is no need\n// to add a highest bucket with +Inf bound, it will be added\n// implicitly. The default value is prometheus.DefBuckets.\nfunc New(opts ...Option) *Factory {\n\toptions := applyOptions(opts)\n\treturn newFactory(\n\t\t&Factory{ // dummy struct to be discarded\n\t\t\tcache:      newVectorCache(options.registerer),\n\t\t\tbuckets:    options.buckets,\n\t\t\tnormalizer: strings.NewReplacer(\".\", \"_\", \"-\", \"_\"),\n\t\t\tseparator:  options.separator,\n\t\t},\n\t\t\"\",  // scope\n\t\tnil) // tags\n}\n\nfunc newFactory(parent *Factory, scope string, tags map[string]string) *Factory {\n\treturn &Factory{\n\t\tcache:      parent.cache,\n\t\tbuckets:    parent.buckets,\n\t\tnormalizer: parent.normalizer,\n\t\tseparator:  parent.separator,\n\t\tscope:      scope,\n\t\ttags:       tags,\n\t}\n}\n\n// Counter implements Counter of metrics.Factory.\nfunc (f *Factory) Counter(options metrics.Options) metrics.Counter {\n\thelp := strings.TrimSpace(options.Help)\n\tif help == \"\" {\n\t\thelp = options.Name\n\t}\n\tname := counterNamingConvention(f.subScope(options.Name))\n\ttags := f.mergeTags(options.Tags)\n\tlabelNames := f.tagNames(tags)\n\topts := prometheus.CounterOpts{\n\t\tName: name,\n\t\tHelp: help,\n\t}\n\tcv := f.cache.getOrMakeCounterVec(opts, labelNames)\n\tlabelValues := f.tagsAsLabelValues(labelNames, tags)\n\tmetric, err := cv.GetMetricWithLabelValues(labelValues...)\n\tif err != nil {\n\t\treturn metrics.NullCounter\n\t}\n\n\treturn &counter{\n\t\tcounter: metric,\n\t}\n}\n\n// Gauge implements Gauge of metrics.Factory.\nfunc (f *Factory) Gauge(options metrics.Options) metrics.Gauge {\n\thelp := strings.TrimSpace(options.Help)\n\tif help == \"\" {\n\t\thelp = options.Name\n\t}\n\tname := f.subScope(options.Name)\n\ttags := f.mergeTags(options.Tags)\n\tlabelNames := f.tagNames(tags)\n\topts := prometheus.GaugeOpts{\n\t\tName: name,\n\t\tHelp: help,\n\t}\n\tgv := f.cache.getOrMakeGaugeVec(opts, labelNames)\n\tlabelValues := f.tagsAsLabelValues(labelNames, tags)\n\tmetric, err := gv.GetMetricWithLabelValues(labelValues...)\n\tif err != nil {\n\t\treturn metrics.NullGauge\n\t}\n\treturn &gauge{\n\t\tgauge: metric,\n\t}\n}\n\n// Timer implements Timer of metrics.Factory.\nfunc (f *Factory) Timer(options metrics.TimerOptions) metrics.Timer {\n\thelp := strings.TrimSpace(options.Help)\n\tif help == \"\" {\n\t\thelp = options.Name\n\t}\n\tname := f.subScope(options.Name)\n\tbuckets := f.selectBuckets(asFloatBuckets(options.Buckets))\n\ttags := f.mergeTags(options.Tags)\n\tlabelNames := f.tagNames(tags)\n\topts := prometheus.HistogramOpts{\n\t\tName:    name,\n\t\tHelp:    help,\n\t\tBuckets: buckets,\n\t}\n\thv := f.cache.getOrMakeHistogramVec(opts, labelNames)\n\tlabelValues := f.tagsAsLabelValues(labelNames, tags)\n\tmetric, err := hv.GetMetricWithLabelValues(labelValues...)\n\tif err != nil {\n\t\treturn metrics.NullTimer\n\t}\n\treturn &timer{\n\t\thistogram: metric,\n\t}\n}\n\nfunc asFloatBuckets(buckets []time.Duration) []float64 {\n\tdata := make([]float64, len(buckets))\n\tfor i := range data {\n\t\tdata[i] = float64(buckets[i]) / float64(time.Second)\n\t}\n\treturn data\n}\n\n// Histogram implements Histogram of metrics.Factory.\nfunc (f *Factory) Histogram(options metrics.HistogramOptions) metrics.Histogram {\n\thelp := strings.TrimSpace(options.Help)\n\tif help == \"\" {\n\t\thelp = options.Name\n\t}\n\tname := f.subScope(options.Name)\n\tbuckets := f.selectBuckets(options.Buckets)\n\ttags := f.mergeTags(options.Tags)\n\tlabelNames := f.tagNames(tags)\n\topts := prometheus.HistogramOpts{\n\t\tName:    name,\n\t\tHelp:    help,\n\t\tBuckets: buckets,\n\t}\n\thv := f.cache.getOrMakeHistogramVec(opts, labelNames)\n\tlabelValues := f.tagsAsLabelValues(labelNames, tags)\n\tmetric, err := hv.GetMetricWithLabelValues(labelValues...)\n\tif err != nil {\n\t\treturn metrics.NullHistogram\n\t}\n\n\treturn &histogram{\n\t\thistogram: metric,\n\t}\n}\n\n// Namespace implements Namespace of metrics.Factory.\nfunc (f *Factory) Namespace(scope metrics.NSOptions) metrics.Factory {\n\treturn newFactory(f, f.subScope(scope.Name), f.mergeTags(scope.Tags))\n}\n\ntype counter struct {\n\tcounter prometheus.Counter\n}\n\nfunc (c *counter) Inc(v int64) {\n\tc.counter.Add(float64(v))\n}\n\ntype gauge struct {\n\tgauge prometheus.Gauge\n}\n\nfunc (g *gauge) Update(v int64) {\n\tg.gauge.Set(float64(v))\n}\n\ntype observer interface {\n\tObserve(v float64)\n}\n\ntype timer struct {\n\thistogram observer\n}\n\nfunc (t *timer) Record(v time.Duration) {\n\tt.histogram.Observe(float64(v.Nanoseconds()) / float64(time.Second/time.Nanosecond))\n}\n\ntype histogram struct {\n\thistogram observer\n}\n\nfunc (h *histogram) Record(v float64) {\n\th.histogram.Observe(v)\n}\n\nfunc (f *Factory) subScope(name string) string {\n\tif f.scope == \"\" {\n\t\treturn f.normalize(name)\n\t}\n\tif name == \"\" {\n\t\treturn f.normalize(f.scope)\n\t}\n\treturn f.normalize(f.scope + string(f.separator) + name)\n}\n\nfunc (f *Factory) normalize(v string) string {\n\treturn f.normalizer.Replace(v)\n}\n\nfunc (f *Factory) mergeTags(tags map[string]string) map[string]string {\n\tret := make(map[string]string, len(f.tags)+len(tags))\n\tmaps.Copy(ret, f.tags)\n\tmaps.Copy(ret, tags)\n\treturn ret\n}\n\nfunc (*Factory) tagNames(tags map[string]string) []string {\n\tret := make([]string, 0, len(tags))\n\tfor k := range tags {\n\t\tret = append(ret, k)\n\t}\n\tsort.Strings(ret)\n\treturn ret\n}\n\nfunc (*Factory) tagsAsLabelValues(labels []string, tags map[string]string) []string {\n\tret := make([]string, 0, len(tags))\n\tfor _, l := range labels {\n\t\tret = append(ret, tags[l])\n\t}\n\treturn ret\n}\n\nfunc (f *Factory) selectBuckets(buckets []float64) []float64 {\n\tif len(buckets) > 0 {\n\t\treturn buckets\n\t}\n\treturn f.buckets\n}\n\nfunc counterNamingConvention(name string) string {\n\tif !strings.HasSuffix(name, \"_total\") {\n\t\tname += \"_total\"\n\t}\n\treturn name\n}\n"
  },
  {
    "path": "internal/metrics/prometheus/factory_test.go",
    "content": "// Copyright (c) 2017 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tprommodel \"github.com/prometheus/client_model/go\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tprommetrics \"github.com/jaegertracing/jaeger/internal/metrics/prometheus\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestOptions(t *testing.T) {\n\tf1 := prommetrics.New()\n\tassert.NotNil(t, f1)\n}\n\nfunc TestSeparator(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry), prommetrics.WithSeparator(prommetrics.SeparatorColon))\n\tc1 := f1.Namespace(metrics.NSOptions{\n\t\tName: \"bender\",\n\t}).Counter(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t\tHelp: \"Help message\",\n\t})\n\tc1.Inc(1)\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\tm1 := findMetric(t, snapshot, \"bender:rodriguez_total\", map[string]string{\"a\": \"b\"})\n\tassert.InDelta(t, 1.0, m1.GetCounter().GetValue(), 0.01, \"%+v\", m1)\n}\n\nfunc TestCounter(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tfDummy := f1.Namespace(metrics.NSOptions{})\n\tf2 := fDummy.Namespace(metrics.NSOptions{\n\t\tName: \"bender\",\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t})\n\tf3 := f2.Namespace(metrics.NSOptions{})\n\n\tc1 := f2.Counter(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t\tHelp: \"Help message\",\n\t})\n\tc2 := f2.Counter(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t})\n\tc3 := f3.Counter(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t}) // same tags as c2, but from f3\n\tc1.Inc(1)\n\tc1.Inc(2)\n\tc2.Inc(3)\n\tc3.Inc(4)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"Help message\", snapshot[0].GetHelp())\n\n\tm1 := findMetric(t, snapshot, \"bender_rodriguez_total\", map[string]string{\"a\": \"b\", \"x\": \"y\"})\n\tassert.InDelta(t, 3.0, m1.GetCounter().GetValue(), 0.01, \"%+v\", m1)\n\n\tm2 := findMetric(t, snapshot, \"bender_rodriguez_total\", map[string]string{\"a\": \"b\", \"x\": \"z\"})\n\tassert.InDelta(t, 7.0, m2.GetCounter().GetValue(), 0.01, \"%+v\", m2)\n}\n\nfunc TestCounterDefaultHelp(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tc1 := f1.Counter(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t})\n\tc1.Inc(1)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"rodriguez\", snapshot[0].GetHelp())\n}\n\nfunc TestCounterNotValidLabel(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(\n\t\tprommetrics.WithRegisterer(registry),\n\t)\n\tc1 := f1.Counter(metrics.Options{\n\t\tName: \"ilia\",\n\t\tTags: map[string]string{\"x\": \"label__3d\\x85_this_will_panic-repro\"},\n\t})\n\tassert.Equal(t, c1, metrics.NullCounter, \"Expected NullCounter, got %v\", c1)\n}\n\nfunc TestGauge(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tf2 := f1.Namespace(metrics.NSOptions{\n\t\tName: \"bender\",\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t})\n\tf3 := f2.Namespace(metrics.NSOptions{\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t}) // essentially same as f2\n\tg1 := f2.Gauge(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t\tHelp: \"Help message\",\n\t})\n\tg2 := f2.Gauge(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t})\n\tg3 := f3.Gauge(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t}) // same as g2, but from f3\n\tg1.Update(1)\n\tg1.Update(2)\n\tg2.Update(3)\n\tg3.Update(4)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"Help message\", snapshot[0].GetHelp())\n\n\tm1 := findMetric(t, snapshot, \"bender_rodriguez\", map[string]string{\"a\": \"b\", \"x\": \"y\"})\n\tassert.InDelta(t, 2.0, m1.GetGauge().GetValue(), 0.01, \"%+v\", m1)\n\n\tm2 := findMetric(t, snapshot, \"bender_rodriguez\", map[string]string{\"a\": \"b\", \"x\": \"z\"})\n\tassert.InDelta(t, 4.0, m2.GetGauge().GetValue(), 0.01, \"%+v\", m2)\n}\n\nfunc TestGaugeDefaultHelp(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tg1 := f1.Gauge(metrics.Options{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t})\n\tg1.Update(1)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"rodriguez\", snapshot[0].GetHelp())\n}\n\nfunc TestGaugeNotValidLabel(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(\n\t\tprommetrics.WithRegisterer(registry),\n\t)\n\tc1 := f1.Gauge(metrics.Options{\n\t\tName: \"ilia\",\n\t\tTags: map[string]string{\"x\": \"label__3d\\x85_this_will_panic-repro\"},\n\t})\n\tassert.Equal(t, c1, metrics.NullGauge, \"Expected NullGauge, got %v\", c1)\n}\n\nfunc TestTimer(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tf2 := f1.Namespace(metrics.NSOptions{\n\t\tName: \"bender\",\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t})\n\tf3 := f2.Namespace(metrics.NSOptions{\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t}) // essentially same as f2\n\tt1 := f2.Timer(metrics.TimerOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t\tHelp: \"Help message\",\n\t})\n\tt2 := f2.Timer(metrics.TimerOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t})\n\tt3 := f3.Timer(metrics.TimerOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t}) // same as t2, but from f3\n\tt1.Record(1 * time.Second)\n\tt1.Record(2 * time.Second)\n\tt2.Record(3 * time.Second)\n\tt3.Record(4 * time.Second)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"Help message\", snapshot[0].GetHelp())\n\n\tm1 := findMetric(t, snapshot, \"bender_rodriguez\", map[string]string{\"a\": \"b\", \"x\": \"y\"})\n\tassert.EqualValues(t, 2, m1.GetHistogram().GetSampleCount(), \"%+v\", m1)\n\tassert.InDelta(t, 3.0, m1.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m1)\n\tfor _, bucket := range m1.GetHistogram().GetBucket() {\n\t\tswitch {\n\t\tcase bucket.GetUpperBound() < 1:\n\t\t\tassert.EqualValues(t, 0, bucket.GetCumulativeCount())\n\t\tcase bucket.GetUpperBound() < 2:\n\t\t\tassert.EqualValues(t, 1, bucket.GetCumulativeCount())\n\t\tdefault:\n\t\t\tassert.EqualValues(t, 2, bucket.GetCumulativeCount())\n\t\t}\n\t}\n\n\tm2 := findMetric(t, snapshot, \"bender_rodriguez\", map[string]string{\"a\": \"b\", \"x\": \"z\"})\n\tassert.EqualValues(t, 2, m2.GetHistogram().GetSampleCount(), \"%+v\", m2)\n\tassert.InDelta(t, 7.0, m2.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m2)\n\tfor _, bucket := range m2.GetHistogram().GetBucket() {\n\t\tswitch {\n\t\tcase bucket.GetUpperBound() < 3:\n\t\t\tassert.EqualValues(t, 0, bucket.GetCumulativeCount())\n\t\tcase bucket.GetUpperBound() < 4:\n\t\t\tassert.EqualValues(t, 1, bucket.GetCumulativeCount())\n\t\tdefault:\n\t\t\tassert.EqualValues(t, 2, bucket.GetCumulativeCount())\n\t\t}\n\t}\n}\n\nfunc TestTimerDefaultHelp(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tt1 := f1.Timer(metrics.TimerOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t})\n\tt1.Record(1 * time.Second)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"rodriguez\", snapshot[0].GetHelp())\n}\n\nfunc TestTimerNotValidLabel(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(\n\t\tprommetrics.WithRegisterer(registry),\n\t)\n\tc1 := f1.Timer(metrics.TimerOptions{\n\t\tName: \"ilia\",\n\t\tTags: map[string]string{\"x\": \"label__3d\\x85_this_will_panic-repro\"},\n\t})\n\tassert.Equal(t, c1, metrics.NullTimer, \"Expected NullTimer, got %v\", c1)\n}\n\nfunc TestTimerCustomBuckets(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry), prommetrics.WithBuckets([]float64{1.5}))\n\t// dot and dash in the metric name will be replaced with underscore\n\tt1 := f1.Timer(metrics.TimerOptions{\n\t\tName:    \"bender.bending-rodriguez\",\n\t\tTags:    map[string]string{\"x\": \"y\"},\n\t\tBuckets: []time.Duration{time.Nanosecond, 5 * time.Nanosecond},\n\t})\n\tt1.Record(1 * time.Second)\n\tt1.Record(2 * time.Second)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tm1 := findMetric(t, snapshot, \"bender_bending_rodriguez\", map[string]string{\"x\": \"y\"})\n\tassert.EqualValues(t, 2, m1.GetHistogram().GetSampleCount(), \"%+v\", m1)\n\tassert.InDelta(t, 3.0, m1.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m1)\n\tassert.Len(t, m1.GetHistogram().GetBucket(), 2)\n}\n\nfunc TestTimerDefaultBuckets(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry), prommetrics.WithBuckets([]float64{1.5, 2}))\n\t// dot and dash in the metric name will be replaced with underscore\n\tt1 := f1.Timer(metrics.TimerOptions{\n\t\tName:    \"bender.bending-rodriguez\",\n\t\tTags:    map[string]string{\"x\": \"y\"},\n\t\tBuckets: nil,\n\t})\n\tt1.Record(1 * time.Second)\n\tt1.Record(2 * time.Second)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tm1 := findMetric(t, snapshot, \"bender_bending_rodriguez\", map[string]string{\"x\": \"y\"})\n\tassert.EqualValues(t, 2, m1.GetHistogram().GetSampleCount(), \"%+v\", m1)\n\tassert.InDelta(t, 3.0, m1.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m1)\n\tassert.Len(t, m1.GetHistogram().GetBucket(), 2)\n}\n\nfunc TestHistogram(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tf2 := f1.Namespace(metrics.NSOptions{\n\t\tName: \"bender\",\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t})\n\tf3 := f2.Namespace(metrics.NSOptions{\n\t\tTags: map[string]string{\"a\": \"b\"},\n\t}) // essentially same as f2\n\tt1 := f2.Histogram(metrics.HistogramOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t\tHelp: \"Help message\",\n\t})\n\tt2 := f2.Histogram(metrics.HistogramOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t})\n\tt3 := f3.Histogram(metrics.HistogramOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"z\"},\n\t\tHelp: \"Help message\",\n\t}) // same as t2, but from f3\n\tt1.Record(1)\n\tt1.Record(2)\n\tt2.Record(3)\n\tt3.Record(4)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"Help message\", snapshot[0].GetHelp())\n\n\tm1 := findMetric(t, snapshot, \"bender_rodriguez\", map[string]string{\"a\": \"b\", \"x\": \"y\"})\n\tassert.EqualValues(t, 2, m1.GetHistogram().GetSampleCount(), \"%+v\", m1)\n\tassert.InDelta(t, 3.0, m1.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m1)\n\tfor _, bucket := range m1.GetHistogram().GetBucket() {\n\t\tswitch {\n\t\tcase bucket.GetUpperBound() < 1:\n\t\t\tassert.EqualValues(t, 0, bucket.GetCumulativeCount())\n\t\tcase bucket.GetUpperBound() < 2:\n\t\t\tassert.EqualValues(t, 1, bucket.GetCumulativeCount())\n\t\tdefault:\n\t\t\tassert.EqualValues(t, 2, bucket.GetCumulativeCount())\n\t\t}\n\t}\n\n\tm2 := findMetric(t, snapshot, \"bender_rodriguez\", map[string]string{\"a\": \"b\", \"x\": \"z\"})\n\tassert.EqualValues(t, 2, m2.GetHistogram().GetSampleCount(), \"%+v\", m2)\n\tassert.InDelta(t, 7.0, m2.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m2)\n\tfor _, bucket := range m2.GetHistogram().GetBucket() {\n\t\tswitch {\n\t\tcase bucket.GetUpperBound() < 3:\n\t\t\tassert.EqualValues(t, 0, bucket.GetCumulativeCount())\n\t\tcase bucket.GetUpperBound() < 4:\n\t\t\tassert.EqualValues(t, 1, bucket.GetCumulativeCount())\n\t\tdefault:\n\t\t\tassert.EqualValues(t, 2, bucket.GetCumulativeCount())\n\t\t}\n\t}\n}\n\nfunc TestHistogramDefaultHelp(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\tt1 := f1.Histogram(metrics.HistogramOptions{\n\t\tName: \"rodriguez\",\n\t\tTags: map[string]string{\"x\": \"y\"},\n\t})\n\tt1.Record(1)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"rodriguez\", snapshot[0].GetHelp())\n}\n\nfunc TestHistogramCustomBuckets(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry))\n\t// dot and dash in the metric name will be replaced with underscore\n\tt1 := f1.Histogram(metrics.HistogramOptions{\n\t\tName:    \"bender.bending-rodriguez\",\n\t\tTags:    map[string]string{\"x\": \"y\"},\n\t\tBuckets: []float64{1.5},\n\t})\n\tt1.Record(1)\n\tt1.Record(2)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tm1 := findMetric(t, snapshot, \"bender_bending_rodriguez\", map[string]string{\"x\": \"y\"})\n\tassert.EqualValues(t, 2, m1.GetHistogram().GetSampleCount(), \"%+v\", m1)\n\tassert.InDelta(t, 3.0, m1.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m1)\n\tassert.Len(t, m1.GetHistogram().GetBucket(), 1)\n}\n\nfunc TestHistogramNotValidLabel(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(\n\t\tprommetrics.WithRegisterer(registry),\n\t)\n\tc1 := f1.Histogram(metrics.HistogramOptions{\n\t\tName: \"ilia\",\n\t\tTags: map[string]string{\"x\": \"label__3d\\x85_this_will_panic-repro\"},\n\t})\n\tassert.Equal(t, c1, metrics.NullHistogram, \"Expected NullHistogram, got %v\", c1)\n}\n\nfunc TestHistogramDefaultBuckets(t *testing.T) {\n\tregistry := prometheus.NewPedanticRegistry()\n\tf1 := prommetrics.New(prommetrics.WithRegisterer(registry), prommetrics.WithBuckets([]float64{1.5}))\n\t// dot and dash in the metric name will be replaced with underscore\n\tt1 := f1.Histogram(metrics.HistogramOptions{\n\t\tName:    \"bender.bending-rodriguez\",\n\t\tTags:    map[string]string{\"x\": \"y\"},\n\t\tBuckets: nil,\n\t})\n\tt1.Record(1)\n\tt1.Record(2)\n\n\tsnapshot, err := registry.Gather()\n\trequire.NoError(t, err)\n\n\tm1 := findMetric(t, snapshot, \"bender_bending_rodriguez\", map[string]string{\"x\": \"y\"})\n\tassert.EqualValues(t, 2, m1.GetHistogram().GetSampleCount(), \"%+v\", m1)\n\tassert.InDelta(t, 3.0, m1.GetHistogram().GetSampleSum(), 0.01, \"%+v\", m1)\n\tassert.Len(t, m1.GetHistogram().GetBucket(), 1)\n}\n\nfunc findMetric(t *testing.T, snapshot []*prommodel.MetricFamily, name string, tags map[string]string) *prommodel.Metric {\n\tfor _, mf := range snapshot {\n\t\tif mf.GetName() != name {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, m := range mf.GetMetric() {\n\t\t\trequire.Lenf(t, m.GetLabel(), len(tags), \"Mismatching labels for metric %v: want %v, have %v\", name, tags, m.GetLabel())\n\t\t\tmatch := true\n\t\t\tfor _, l := range m.GetLabel() {\n\t\t\t\tif v, ok := tags[l.GetName()]; !ok || v != l.GetValue() {\n\t\t\t\t\tmatch = false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif match {\n\t\t\t\treturn m\n\t\t\t}\n\t\t}\n\t}\n\tt.Logf(\"Cannot find metric %v %v\", name, tags)\n\tfor _, nf := range snapshot {\n\t\tt.Logf(\"Family: %v\", nf.GetName())\n\t\tfor _, m := range nf.GetMetric() {\n\t\t\tt.Logf(\"==> %v\", m)\n\t\t}\n\t}\n\tt.FailNow()\n\treturn nil\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/metrics/stopwatch.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\nimport (\n\t\"time\"\n)\n\n// StartStopwatch begins recording the executing time of an event, returning\n// a Stopwatch that should be used to stop the recording the time for\n// that event.  Multiple events can be occurring simultaneously each\n// represented by different active Stopwatches\nfunc StartStopwatch(timer Timer) Stopwatch {\n\treturn Stopwatch{t: timer, start: time.Now()}\n}\n\n// A Stopwatch tracks the execution time of a specific event\ntype Stopwatch struct {\n\tt     Timer\n\tstart time.Time\n}\n\n// Stop stops executing of the stopwatch and records the amount of elapsed time\nfunc (s Stopwatch) Stop() {\n\ts.t.Record(s.ElapsedTime())\n}\n\n// ElapsedTime returns the amount of elapsed time (in time.Duration)\nfunc (s Stopwatch) ElapsedTime() time.Duration {\n\treturn time.Since(s.start)\n}\n"
  },
  {
    "path": "internal/metrics/timer.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\nimport (\n\t\"time\"\n)\n\n// Timer accumulates observations about how long some operation took,\n// and also maintains a historgam of percentiles.\ntype Timer interface {\n\t// Records the time passed in.\n\tRecord(time.Duration)\n}\n\n// NullTimer timer that does nothing\nvar NullTimer Timer = nullTimer{}\n\ntype nullTimer struct{}\n\nfunc (nullTimer) Record(time.Duration) {}\n"
  },
  {
    "path": "internal/metricstest/keys.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstest\n\nimport (\n\t\"sort\"\n)\n\n// GetKey converts name+tags into a single string of the form\n// \"name|tag1=value1|...|tagN=valueN\", where tag names are\n// sorted alphabetically.\nfunc GetKey(name string, tags map[string]string, tagsSep string, tagKVSep string) string {\n\tkeys := make([]string, 0, len(tags))\n\tfor k := range tags {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tkey := name\n\tfor _, k := range keys {\n\t\tkey = key + tagsSep + k + tagKVSep + tags[k]\n\t}\n\treturn key\n}\n"
  },
  {
    "path": "internal/metricstest/local.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstest\n\nimport (\n\t\"maps\"\n\t\"slices\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\n// This is intentionally very similar to github.com/codahale/metrics, the\n// main difference being that counters/gauges are scoped to the provider\n// rather than being global (to facilitate testing).\n\n// numeric is a constraint that permits int64 and float64.\ntype numeric interface {\n\t~int64 | ~float64\n}\n\n// simpleHistogram is a simple histogram that stores all observations\n// and computes percentiles from a sorted list. It uses generics to\n// support both int64 (for timers) and float64 (for histograms).\ntype simpleHistogram[T numeric] struct {\n\tsync.Mutex\n\tobservations []T\n}\n\nfunc (h *simpleHistogram[T]) record(v T) {\n\th.Lock()\n\tdefer h.Unlock()\n\th.observations = append(h.observations, v)\n}\n\nfunc (h *simpleHistogram[T]) valueAtPercentile(q float64) int64 {\n\th.Lock()\n\tdefer h.Unlock()\n\tif len(h.observations) == 0 {\n\t\treturn 0\n\t}\n\tsorted := slices.Clone(h.observations)\n\tslices.Sort(sorted)\n\n\tidx := int(float64(len(sorted)-1) * q / 100.0)\n\treturn int64(sorted[idx])\n}\n\n// A Backend is a metrics provider which aggregates data in-vm, and\n// allows exporting snapshots to shove the data into a remote collector\ntype Backend struct {\n\tcm         sync.Mutex\n\tgm         sync.Mutex\n\ttm         sync.Mutex\n\thm         sync.Mutex\n\tcounters   map[string]*int64\n\tgauges     map[string]*int64\n\ttimers     map[string]*simpleHistogram[int64]\n\thistograms map[string]*simpleHistogram[float64]\n\tTagsSep    string\n\tTagKVSep   string\n}\n\n// NewBackend returns a new Backend. The collectionInterval is the histogram\n// time window for each timer.\nfunc NewBackend(_ time.Duration) *Backend {\n\treturn &Backend{\n\t\tcounters:   make(map[string]*int64),\n\t\tgauges:     make(map[string]*int64),\n\t\ttimers:     make(map[string]*simpleHistogram[int64]),\n\t\thistograms: make(map[string]*simpleHistogram[float64]),\n\t\tTagsSep:    \"|\",\n\t\tTagKVSep:   \"=\",\n\t}\n}\n\n// Clear discards accumulated stats\nfunc (b *Backend) Clear() {\n\tb.cm.Lock()\n\tdefer b.cm.Unlock()\n\tb.gm.Lock()\n\tdefer b.gm.Unlock()\n\tb.tm.Lock()\n\tdefer b.tm.Unlock()\n\tb.hm.Lock()\n\tdefer b.hm.Unlock()\n\tb.counters = make(map[string]*int64)\n\tb.gauges = make(map[string]*int64)\n\tb.timers = make(map[string]*simpleHistogram[int64])\n\tb.histograms = make(map[string]*simpleHistogram[float64])\n}\n\n// IncCounter increments a counter value\nfunc (b *Backend) IncCounter(name string, tags map[string]string, delta int64) {\n\tname = GetKey(name, tags, b.TagsSep, b.TagKVSep)\n\tb.cm.Lock()\n\tdefer b.cm.Unlock()\n\tcounter := b.counters[name]\n\tif counter == nil {\n\t\tb.counters[name] = new(int64)\n\t\t*b.counters[name] = delta\n\t\treturn\n\t}\n\tatomic.AddInt64(counter, delta)\n}\n\n// UpdateGauge updates the value of a gauge\nfunc (b *Backend) UpdateGauge(name string, tags map[string]string, value int64) {\n\tname = GetKey(name, tags, b.TagsSep, b.TagKVSep)\n\tb.gm.Lock()\n\tdefer b.gm.Unlock()\n\tgauge := b.gauges[name]\n\tif gauge == nil {\n\t\tb.gauges[name] = new(int64)\n\t\t*b.gauges[name] = value\n\t\treturn\n\t}\n\tatomic.StoreInt64(gauge, value)\n}\n\n// RecordHistogram records a histogram value\nfunc (b *Backend) RecordHistogram(name string, tags map[string]string, v float64) {\n\tname = GetKey(name, tags, b.TagsSep, b.TagKVSep)\n\thistogram := b.findOrCreateHistogram(name)\n\thistogram.record(v)\n}\n\nfunc (b *Backend) findOrCreateHistogram(name string) *simpleHistogram[float64] {\n\tb.hm.Lock()\n\tdefer b.hm.Unlock()\n\tif h, ok := b.histograms[name]; ok {\n\t\treturn h\n\t}\n\th := &simpleHistogram[float64]{}\n\tb.histograms[name] = h\n\treturn h\n}\n\n// RecordTimer records a timing duration\nfunc (b *Backend) RecordTimer(name string, tags map[string]string, d time.Duration) {\n\tname = GetKey(name, tags, b.TagsSep, b.TagKVSep)\n\ttimer := b.findOrCreateTimer(name)\n\ttimer.record(int64(d / time.Millisecond))\n}\n\nfunc (b *Backend) findOrCreateTimer(name string) *simpleHistogram[int64] {\n\tb.tm.Lock()\n\tdefer b.tm.Unlock()\n\tif t, ok := b.timers[name]; ok {\n\t\treturn t\n\t}\n\tt := &simpleHistogram[int64]{}\n\tb.timers[name] = t\n\treturn t\n}\n\nvar percentiles = map[string]float64{\n\t\"P50\":  50,\n\t\"P75\":  75,\n\t\"P90\":  90,\n\t\"P95\":  95,\n\t\"P99\":  99,\n\t\"P999\": 99.9,\n}\n\n// Snapshot captures a snapshot of the current counter and gauge values\nfunc (b *Backend) Snapshot() (counters, gauges map[string]int64) {\n\tb.cm.Lock()\n\tdefer b.cm.Unlock()\n\n\tcounters = make(map[string]int64, len(b.counters))\n\tfor name, value := range b.counters {\n\t\tcounters[name] = atomic.LoadInt64(value)\n\t}\n\n\tb.gm.Lock()\n\tdefer b.gm.Unlock()\n\n\tgauges = make(map[string]int64, len(b.gauges))\n\tfor name, value := range b.gauges {\n\t\tgauges[name] = atomic.LoadInt64(value)\n\t}\n\n\tb.tm.Lock()\n\ttimers := make(map[string]*simpleHistogram[int64])\n\tmaps.Copy(timers, b.timers)\n\tb.tm.Unlock()\n\n\tfor timerName, timer := range timers {\n\t\tfor name, q := range percentiles {\n\t\t\tgauges[timerName+\".\"+name] = timer.valueAtPercentile(q)\n\t\t}\n\t}\n\n\tb.hm.Lock()\n\thistograms := make(map[string]*simpleHistogram[float64])\n\tmaps.Copy(histograms, b.histograms)\n\tb.hm.Unlock()\n\n\tfor histogramName, histogram := range histograms {\n\t\tfor name, q := range percentiles {\n\t\t\tgauges[histogramName+\".\"+name] = histogram.valueAtPercentile(q)\n\t\t}\n\t}\n\n\treturn counters, gauges\n}\n\n// Stop is a no-op for this simple backend (no background goroutines).\nfunc (*Backend) Stop() {}\n\ntype stats struct {\n\tname            string\n\ttags            map[string]string\n\tbuckets         []float64\n\tdurationBuckets []time.Duration\n\tlocalBackend    *Backend\n}\n\ntype localTimer struct {\n\tstats\n}\n\nfunc (l *localTimer) Record(d time.Duration) {\n\tl.localBackend.RecordTimer(l.name, l.tags, d)\n}\n\ntype localHistogram struct {\n\tstats\n}\n\nfunc (l *localHistogram) Record(v float64) {\n\tl.localBackend.RecordHistogram(l.name, l.tags, v)\n}\n\ntype localCounter struct {\n\tstats\n}\n\nfunc (l *localCounter) Inc(delta int64) {\n\tl.localBackend.IncCounter(l.name, l.tags, delta)\n}\n\ntype localGauge struct {\n\tstats\n}\n\nfunc (l *localGauge) Update(value int64) {\n\tl.localBackend.UpdateGauge(l.name, l.tags, value)\n}\n\n// Factory stats factory that creates metrics that are stored locally\ntype Factory struct {\n\t*Backend\n\tnamespace string\n\ttags      map[string]string\n}\n\n// NewFactory returns a new LocalMetricsFactory\nfunc NewFactory(collectionInterval time.Duration) *Factory {\n\treturn &Factory{\n\t\tBackend: NewBackend(collectionInterval),\n\t}\n}\n\n// appendTags adds the tags to the namespace tags and returns a combined map.\nfunc (f *Factory) appendTags(tags map[string]string) map[string]string {\n\tnewTags := make(map[string]string)\n\tmaps.Copy(newTags, f.tags)\n\tmaps.Copy(newTags, tags)\n\treturn newTags\n}\n\nfunc (f *Factory) newNamespace(name string) string {\n\tif f.namespace == \"\" {\n\t\treturn name\n\t}\n\n\tif name == \"\" {\n\t\treturn f.namespace\n\t}\n\n\treturn f.namespace + \".\" + name\n}\n\n// Counter returns a local stats counter\nfunc (f *Factory) Counter(options metrics.Options) metrics.Counter {\n\treturn &localCounter{\n\t\tstats{\n\t\t\tname:         f.newNamespace(options.Name),\n\t\t\ttags:         f.appendTags(options.Tags),\n\t\t\tlocalBackend: f.Backend,\n\t\t},\n\t}\n}\n\n// Timer returns a local stats timer.\nfunc (f *Factory) Timer(options metrics.TimerOptions) metrics.Timer {\n\treturn &localTimer{\n\t\tstats{\n\t\t\tname:            f.newNamespace(options.Name),\n\t\t\ttags:            f.appendTags(options.Tags),\n\t\t\tdurationBuckets: options.Buckets,\n\t\t\tlocalBackend:    f.Backend,\n\t\t},\n\t}\n}\n\n// Gauge returns a local stats gauge.\nfunc (f *Factory) Gauge(options metrics.Options) metrics.Gauge {\n\treturn &localGauge{\n\t\tstats{\n\t\t\tname:         f.newNamespace(options.Name),\n\t\t\ttags:         f.appendTags(options.Tags),\n\t\t\tlocalBackend: f.Backend,\n\t\t},\n\t}\n}\n\n// Histogram returns a local stats histogram.\nfunc (f *Factory) Histogram(options metrics.HistogramOptions) metrics.Histogram {\n\treturn &localHistogram{\n\t\tstats{\n\t\t\tname:         f.newNamespace(options.Name),\n\t\t\ttags:         f.appendTags(options.Tags),\n\t\t\tbuckets:      options.Buckets,\n\t\t\tlocalBackend: f.Backend,\n\t\t},\n\t}\n}\n\n// Namespace returns a new namespace.\nfunc (f *Factory) Namespace(scope metrics.NSOptions) metrics.Factory {\n\treturn &Factory{\n\t\tnamespace: f.newNamespace(scope.Name),\n\t\ttags:      f.appendTags(scope.Tags),\n\t\tBackend:   f.Backend,\n\t}\n}\n\nfunc (f *Factory) Stop() {\n\tf.Backend.Stop()\n}\n"
  },
  {
    "path": "internal/metricstest/local_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstest\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\nfunc TestLocalMetrics(t *testing.T) {\n\ttags := map[string]string{\n\t\t\"x\": \"y\",\n\t}\n\n\tf := NewFactory(0)\n\tdefer f.Stop()\n\tf.Counter(metrics.Options{\n\t\tName: \"my-counter\",\n\t\tTags: tags,\n\t}).Inc(4)\n\tf.Counter(metrics.Options{\n\t\tName: \"my-counter\",\n\t\tTags: tags,\n\t}).Inc(6)\n\tf.Counter(metrics.Options{\n\t\tName: \"my-counter\",\n\t}).Inc(6)\n\tf.Counter(metrics.Options{\n\t\tName: \"other-counter\",\n\t}).Inc(8)\n\tf.Gauge(metrics.Options{\n\t\tName: \"my-gauge\",\n\t}).Update(25)\n\tf.Gauge(metrics.Options{\n\t\tName: \"my-gauge\",\n\t}).Update(43)\n\tf.Gauge(metrics.Options{\n\t\tName: \"other-gauge\",\n\t}).Update(74)\n\tf.Namespace(metrics.NSOptions{\n\t\tName: \"namespace\",\n\t\tTags: tags,\n\t}).Counter(metrics.Options{\n\t\tName: \"my-counter\",\n\t}).Inc(7)\n\tf.Namespace(metrics.NSOptions{\n\t\tName: \"ns.subns\",\n\t}).Counter(metrics.Options{\n\t\tTags: map[string]string{\"service\": \"a-service\"},\n\t}).Inc(9)\n\n\ttimings := map[string][]time.Duration{\n\t\t\"foo-latency\": {\n\t\t\ttime.Second * 35,\n\t\t\ttime.Second * 6,\n\t\t\ttime.Millisecond * 576,\n\t\t\ttime.Second * 12,\n\t\t},\n\t\t\"bar-latency\": {\n\t\t\ttime.Minute*4 + time.Second*34,\n\t\t\ttime.Minute*7 + time.Second*12,\n\t\t\ttime.Second * 625,\n\t\t\ttime.Second * 12,\n\t\t},\n\t}\n\n\tfor metric, timing := range timings {\n\t\tfor _, d := range timing {\n\t\t\tf.Timer(metrics.TimerOptions{\n\t\t\t\tName: metric,\n\t\t\t}).Record(d)\n\t\t}\n\t}\n\n\thistogram := f.Histogram(metrics.HistogramOptions{\n\t\tName: \"my-histo\",\n\t})\n\thistogram.Record(321)\n\thistogram.Record(42)\n\n\tc, g := f.Snapshot()\n\trequire.NotNil(t, c)\n\trequire.NotNil(t, g)\n\n\tassert.Equal(t, map[string]int64{\n\t\t\"my-counter|x=y\":             10,\n\t\t\"my-counter\":                 6,\n\t\t\"other-counter\":              8,\n\t\t\"namespace.my-counter|x=y\":   7,\n\t\t\"ns.subns|service=a-service\": 9,\n\t}, c)\n\n\tassert.Equal(t, map[string]int64{\n\t\t\"bar-latency.P50\":  274000,\n\t\t\"bar-latency.P75\":  432000,\n\t\t\"bar-latency.P90\":  432000,\n\t\t\"bar-latency.P95\":  432000,\n\t\t\"bar-latency.P99\":  432000,\n\t\t\"bar-latency.P999\": 432000,\n\t\t\"foo-latency.P50\":  6000,\n\t\t\"foo-latency.P75\":  12000,\n\t\t\"foo-latency.P90\":  12000,\n\t\t\"foo-latency.P95\":  12000,\n\t\t\"foo-latency.P99\":  12000,\n\t\t\"foo-latency.P999\": 12000,\n\t\t\"my-gauge\":         43,\n\t\t\"my-histo.P50\":     42,\n\t\t\"my-histo.P75\":     42,\n\t\t\"my-histo.P90\":     42,\n\t\t\"my-histo.P95\":     42,\n\t\t\"my-histo.P99\":     42,\n\t\t\"my-histo.P999\":    42,\n\t\t\"other-gauge\":      74,\n\t}, g)\n\n\tf.Clear()\n\tc, g = f.Snapshot()\n\trequire.Empty(t, c)\n\trequire.Empty(t, g)\n}\n\nfunc TestLocalMetricsInterval(t *testing.T) {\n\tf := NewFactory(time.Millisecond)\n\tdefer f.Stop()\n\n\tf.Timer(metrics.TimerOptions{\n\t\tName: \"timer\",\n\t}).Record(time.Millisecond * 100)\n\n\tf.tm.Lock()\n\ttimer := f.timers[\"timer\"]\n\tf.tm.Unlock()\n\trequire.NotNil(t, timer)\n\n\ttimer.Lock()\n\tassert.Len(t, timer.observations, 1)\n\tassert.Equal(t, int64(100), timer.observations[0])\n\ttimer.Unlock()\n}\n"
  },
  {
    "path": "internal/metricstest/metricstest.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// ExpectedMetric contains metrics under test.\ntype ExpectedMetric struct {\n\tName  string\n\tTags  map[string]string\n\tValue int\n}\n\n// ExpectedTimerMetric contains timer metrics under test.\ntype ExpectedTimerMetric struct {\n\tName       string\n\tTags       map[string]string\n\tPercentile string // e.g., \"P50\", \"P75\", \"P90\", \"P95\", \"P99\", \"P999\"\n\tValue      int    // expected value in milliseconds\n}\n\n// AssertTimerMetrics checks if timer metrics exist with expected percentile values.\nfunc (f *Factory) AssertTimerMetrics(t *testing.T, expectedMetrics ...ExpectedTimerMetric) {\n\t_, gauges := f.Snapshot()\n\tfor _, expected := range expectedMetrics {\n\t\tkey := GetKey(expected.Name, expected.Tags, \"|\", \"=\")\n\t\tfullKey := key + \".\" + expected.Percentile\n\t\tassert.EqualValues(t,\n\t\t\texpected.Value,\n\t\t\tgauges[fullKey],\n\t\t\t\"expected timer metric name=%s percentile=%s tags: %+v; got: %+v\",\n\t\t\texpected.Name, expected.Percentile, expected.Tags, gauges,\n\t\t)\n\t}\n}\n\n// AssertCounterMetrics checks if counter metrics exist.\nfunc (f *Factory) AssertCounterMetrics(t *testing.T, expectedMetrics ...ExpectedMetric) {\n\tcounters, _ := f.Snapshot()\n\tassertMetrics(t, counters, expectedMetrics...)\n}\n\n// AssertGaugeMetrics checks if gauge metrics exist.\nfunc (f *Factory) AssertGaugeMetrics(t *testing.T, expectedMetrics ...ExpectedMetric) {\n\t_, gauges := f.Snapshot()\n\tassertMetrics(t, gauges, expectedMetrics...)\n}\n\nfunc assertMetrics(t *testing.T, actualMetrics map[string]int64, expectedMetrics ...ExpectedMetric) {\n\tfor _, expected := range expectedMetrics {\n\t\tkey := GetKey(expected.Name, expected.Tags, \"|\", \"=\")\n\t\tassert.EqualValues(t,\n\t\t\texpected.Value,\n\t\t\tactualMetrics[key],\n\t\t\t\"expected metric name=%s tags: %+v; got: %+v\", expected.Name, expected.Tags, actualMetrics,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "internal/metricstest/metricstest_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstest\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestAssertMetrics(t *testing.T) {\n\tf := NewFactory(0)\n\ttags := map[string]string{\"key\": \"value\"}\n\tf.IncCounter(\"counter\", tags, 1)\n\tf.UpdateGauge(\"gauge\", tags, 11)\n\n\tf.AssertCounterMetrics(t, ExpectedMetric{Name: \"counter\", Tags: tags, Value: 1})\n\tf.AssertGaugeMetrics(t, ExpectedMetric{Name: \"gauge\", Tags: tags, Value: 11})\n}\n\nfunc TestAssertTimerMetrics(t *testing.T) {\n\tf := NewFactory(0)\n\ttags := map[string]string{\"service\": \"test\"}\n\n\t// Record some timer values (in milliseconds: 10, 20, 30, 40, 50)\n\tf.RecordTimer(\"request_duration\", tags, 10*time.Millisecond)\n\tf.RecordTimer(\"request_duration\", tags, 20*time.Millisecond)\n\tf.RecordTimer(\"request_duration\", tags, 30*time.Millisecond)\n\tf.RecordTimer(\"request_duration\", tags, 40*time.Millisecond)\n\tf.RecordTimer(\"request_duration\", tags, 50*time.Millisecond)\n\n\t// With 5 values [10, 20, 30, 40, 50]:\n\t// P50 = sorted[int(4 * 0.50)] = sorted[2] = 30\n\t// P99 = sorted[int(4 * 0.99)] = sorted[3] = 40\n\tf.AssertTimerMetrics(t,\n\t\tExpectedTimerMetric{Name: \"request_duration\", Tags: tags, Percentile: \"P50\", Value: 30},\n\t\tExpectedTimerMetric{Name: \"request_duration\", Tags: tags, Percentile: \"P99\", Value: 40},\n\t)\n}\n"
  },
  {
    "path": "internal/metricstest/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/proto/api_v3/query_service.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: query_service.proto\n\npackage api_v3\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\t_ \"github.com/gogo/protobuf/gogoproto\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\t_ \"github.com/gogo/protobuf/types\"\n\tgithub_com_gogo_protobuf_types \"github.com/gogo/protobuf/types\"\n\tv1 \"github.com/jaegertracing/jaeger/internal/jptrace\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tio \"io\"\n\tmath \"math\"\n\tmath_bits \"math/bits\"\n\ttime \"time\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\nvar _ = time.Kitchen\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\n// Request object to get a trace.\ntype GetTraceRequest struct {\n\t// Hex encoded 64 or 128 bit trace ID.\n\tTraceId string `protobuf:\"bytes,1,opt,name=trace_id,json=traceId,proto3\" json:\"trace_id,omitempty\"`\n\t// Optional. The start time to search trace ID.\n\tStartTime time.Time `protobuf:\"bytes,2,opt,name=start_time,json=startTime,proto3,stdtime\" json:\"start_time\"`\n\t// Optional. The end time to search trace ID.\n\tEndTime time.Time `protobuf:\"bytes,3,opt,name=end_time,json=endTime,proto3,stdtime\" json:\"end_time\"`\n\t// Optional. If set to true, the response will not include any\n\t// enrichments to the trace, such as clock skew adjustment.\n\t// Instead, the trace will be returned exactly as stored.\n\tRawTraces            bool     `protobuf:\"varint,4,opt,name=raw_traces,json=rawTraces,proto3\" json:\"raw_traces,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetTraceRequest) Reset()         { *m = GetTraceRequest{} }\nfunc (m *GetTraceRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetTraceRequest) ProtoMessage()    {}\nfunc (*GetTraceRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{0}\n}\nfunc (m *GetTraceRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetTraceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetTraceRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetTraceRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetTraceRequest.Merge(m, src)\n}\nfunc (m *GetTraceRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetTraceRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetTraceRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetTraceRequest proto.InternalMessageInfo\n\nfunc (m *GetTraceRequest) GetTraceId() string {\n\tif m != nil {\n\t\treturn m.TraceId\n\t}\n\treturn \"\"\n}\n\nfunc (m *GetTraceRequest) GetStartTime() time.Time {\n\tif m != nil {\n\t\treturn m.StartTime\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *GetTraceRequest) GetEndTime() time.Time {\n\tif m != nil {\n\t\treturn m.EndTime\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *GetTraceRequest) GetRawTraces() bool {\n\tif m != nil {\n\t\treturn m.RawTraces\n\t}\n\treturn false\n}\n\n// Query parameters to find traces.\n//\n// All fields form a conjunction (e.g., \"service_name='X' AND operation_name='Y' AND ...\"),\n// except for `search_depth` and `raw_traces`.\n//\n// Fields are matched against individual spans, not the trace level. The results include\n// traces with at least one matching span.\n//\n// The results have no guaranteed ordering.\ntype TraceQueryParameters struct {\n\t// service_name filters spans generated by a specific service.\n\tServiceName string `protobuf:\"bytes,1,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\t// operation_name filters spans by a specific operation / span name.\n\tOperationName string `protobuf:\"bytes,2,opt,name=operation_name,json=operationName,proto3\" json:\"operation_name,omitempty\"`\n\t// attributes contains key-value pairs where the key is the attribute name\n\t// and the value is its string representation. Attributes are matched against\n\t// span and resource attributes. At least one span must match all specified attributes.\n\tAttributes map[string]string `protobuf:\"bytes,3,rep,name=attributes,proto3\" json:\"attributes,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\t// start_time_min is the start of the time interval (inclusive) for the query.\n\t// Only traces with spans that started on or after this time will be returned.\n\t//\n\t// The HTTP API uses RFC-3339ns format.\n\t//\n\t// This field is required.\n\tStartTimeMin time.Time `protobuf:\"bytes,4,opt,name=start_time_min,json=startTimeMin,proto3,stdtime\" json:\"start_time_min\"`\n\t// start_time_max is the end of the time interval (exclusive) for the query.\n\t// Only traces with spans that started before this time will be returned.\n\t//\n\t// The HTTP API uses RFC-3339ns format.\n\t//\n\t// This field is required.\n\tStartTimeMax time.Time `protobuf:\"bytes,5,opt,name=start_time_max,json=startTimeMax,proto3,stdtime\" json:\"start_time_max\"`\n\t// duration_min is the minimum duration of a span in the trace.\n\t// Only traces with spans that lasted at least this long will be returned.\n\t//\n\t// The HTTP API uses Golang's time format (e.g., \"10s\").\n\tDurationMin time.Duration `protobuf:\"bytes,6,opt,name=duration_min,json=durationMin,proto3,stdduration\" json:\"duration_min\"`\n\t// duration_max is the maximum duration of a span in the trace.\n\t// Only traces with spans that lasted at most this long will be returned.\n\t//\n\t// The HTTP API uses Golang's time format (e.g., \"10s\").\n\tDurationMax time.Duration `protobuf:\"bytes,7,opt,name=duration_max,json=durationMax,proto3,stdduration\" json:\"duration_max\"`\n\t// search_depth defines the maximum search depth. Depending on the backend storage implementation,\n\t// this may behave like an SQL `LIMIT` clause. However, some implementations might not support\n\t// precise limits, and a larger value generally results in more traces being returned.\n\tSearchDepth int32 `protobuf:\"varint,8,opt,name=search_depth,json=searchDepth,proto3\" json:\"search_depth,omitempty\"`\n\t// If set to true, the response will exclude any enrichments to the trace, such as clock skew adjustments.\n\t// The trace will be returned exactly as stored.\n\t//\n\t// This field is optional.\n\tRawTraces            bool     `protobuf:\"varint,9,opt,name=raw_traces,json=rawTraces,proto3\" json:\"raw_traces,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *TraceQueryParameters) Reset()         { *m = TraceQueryParameters{} }\nfunc (m *TraceQueryParameters) String() string { return proto.CompactTextString(m) }\nfunc (*TraceQueryParameters) ProtoMessage()    {}\nfunc (*TraceQueryParameters) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{1}\n}\nfunc (m *TraceQueryParameters) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *TraceQueryParameters) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_TraceQueryParameters.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *TraceQueryParameters) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_TraceQueryParameters.Merge(m, src)\n}\nfunc (m *TraceQueryParameters) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *TraceQueryParameters) XXX_DiscardUnknown() {\n\txxx_messageInfo_TraceQueryParameters.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_TraceQueryParameters proto.InternalMessageInfo\n\nfunc (m *TraceQueryParameters) GetServiceName() string {\n\tif m != nil {\n\t\treturn m.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (m *TraceQueryParameters) GetOperationName() string {\n\tif m != nil {\n\t\treturn m.OperationName\n\t}\n\treturn \"\"\n}\n\nfunc (m *TraceQueryParameters) GetAttributes() map[string]string {\n\tif m != nil {\n\t\treturn m.Attributes\n\t}\n\treturn nil\n}\n\nfunc (m *TraceQueryParameters) GetStartTimeMin() time.Time {\n\tif m != nil {\n\t\treturn m.StartTimeMin\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *TraceQueryParameters) GetStartTimeMax() time.Time {\n\tif m != nil {\n\t\treturn m.StartTimeMax\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *TraceQueryParameters) GetDurationMin() time.Duration {\n\tif m != nil {\n\t\treturn m.DurationMin\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetDurationMax() time.Duration {\n\tif m != nil {\n\t\treturn m.DurationMax\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetSearchDepth() int32 {\n\tif m != nil {\n\t\treturn m.SearchDepth\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetRawTraces() bool {\n\tif m != nil {\n\t\treturn m.RawTraces\n\t}\n\treturn false\n}\n\n// Request object to search traces.\ntype FindTracesRequest struct {\n\tQuery                *TraceQueryParameters `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}              `json:\"-\"`\n\tXXX_unrecognized     []byte                `json:\"-\"`\n\tXXX_sizecache        int32                 `json:\"-\"`\n}\n\nfunc (m *FindTracesRequest) Reset()         { *m = FindTracesRequest{} }\nfunc (m *FindTracesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*FindTracesRequest) ProtoMessage()    {}\nfunc (*FindTracesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{2}\n}\nfunc (m *FindTracesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FindTracesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FindTracesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FindTracesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FindTracesRequest.Merge(m, src)\n}\nfunc (m *FindTracesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FindTracesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_FindTracesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FindTracesRequest proto.InternalMessageInfo\n\nfunc (m *FindTracesRequest) GetQuery() *TraceQueryParameters {\n\tif m != nil {\n\t\treturn m.Query\n\t}\n\treturn nil\n}\n\n// Request object to get service names.\ntype GetServicesRequest struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetServicesRequest) Reset()         { *m = GetServicesRequest{} }\nfunc (m *GetServicesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetServicesRequest) ProtoMessage()    {}\nfunc (*GetServicesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{3}\n}\nfunc (m *GetServicesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetServicesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetServicesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetServicesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetServicesRequest.Merge(m, src)\n}\nfunc (m *GetServicesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetServicesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetServicesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetServicesRequest proto.InternalMessageInfo\n\n// Response object to get service names.\ntype GetServicesResponse struct {\n\tServices             []string `protobuf:\"bytes,1,rep,name=services,proto3\" json:\"services,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetServicesResponse) Reset()         { *m = GetServicesResponse{} }\nfunc (m *GetServicesResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetServicesResponse) ProtoMessage()    {}\nfunc (*GetServicesResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{4}\n}\nfunc (m *GetServicesResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetServicesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetServicesResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetServicesResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetServicesResponse.Merge(m, src)\n}\nfunc (m *GetServicesResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetServicesResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetServicesResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetServicesResponse proto.InternalMessageInfo\n\nfunc (m *GetServicesResponse) GetServices() []string {\n\tif m != nil {\n\t\treturn m.Services\n\t}\n\treturn nil\n}\n\n// Request object to get operation names.\ntype GetOperationsRequest struct {\n\t// Required service name.\n\tService string `protobuf:\"bytes,1,opt,name=service,proto3\" json:\"service,omitempty\"`\n\t// Optional span kind.\n\tSpanKind             string   `protobuf:\"bytes,2,opt,name=span_kind,json=spanKind,proto3\" json:\"span_kind,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetOperationsRequest) Reset()         { *m = GetOperationsRequest{} }\nfunc (m *GetOperationsRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetOperationsRequest) ProtoMessage()    {}\nfunc (*GetOperationsRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{5}\n}\nfunc (m *GetOperationsRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetOperationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetOperationsRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetOperationsRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetOperationsRequest.Merge(m, src)\n}\nfunc (m *GetOperationsRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetOperationsRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetOperationsRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetOperationsRequest proto.InternalMessageInfo\n\nfunc (m *GetOperationsRequest) GetService() string {\n\tif m != nil {\n\t\treturn m.Service\n\t}\n\treturn \"\"\n}\n\nfunc (m *GetOperationsRequest) GetSpanKind() string {\n\tif m != nil {\n\t\treturn m.SpanKind\n\t}\n\treturn \"\"\n}\n\n// Operation encapsulates information about operation.\ntype Operation struct {\n\tName                 string   `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tSpanKind             string   `protobuf:\"bytes,2,opt,name=span_kind,json=spanKind,proto3\" json:\"span_kind,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Operation) Reset()         { *m = Operation{} }\nfunc (m *Operation) String() string { return proto.CompactTextString(m) }\nfunc (*Operation) ProtoMessage()    {}\nfunc (*Operation) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{6}\n}\nfunc (m *Operation) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Operation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Operation.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Operation) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Operation.Merge(m, src)\n}\nfunc (m *Operation) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Operation) XXX_DiscardUnknown() {\n\txxx_messageInfo_Operation.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Operation proto.InternalMessageInfo\n\nfunc (m *Operation) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Operation) GetSpanKind() string {\n\tif m != nil {\n\t\treturn m.SpanKind\n\t}\n\treturn \"\"\n}\n\n// Response object to get operation names.\ntype GetOperationsResponse struct {\n\tOperations           []*Operation `protobuf:\"bytes,1,rep,name=operations,proto3\" json:\"operations,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}     `json:\"-\"`\n\tXXX_unrecognized     []byte       `json:\"-\"`\n\tXXX_sizecache        int32        `json:\"-\"`\n}\n\nfunc (m *GetOperationsResponse) Reset()         { *m = GetOperationsResponse{} }\nfunc (m *GetOperationsResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetOperationsResponse) ProtoMessage()    {}\nfunc (*GetOperationsResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{7}\n}\nfunc (m *GetOperationsResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetOperationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetOperationsResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetOperationsResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetOperationsResponse.Merge(m, src)\n}\nfunc (m *GetOperationsResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetOperationsResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetOperationsResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetOperationsResponse proto.InternalMessageInfo\n\nfunc (m *GetOperationsResponse) GetOperations() []*Operation {\n\tif m != nil {\n\t\treturn m.Operations\n\t}\n\treturn nil\n}\n\n// GRPCGatewayError is the type returned when GRPC server returns an error.\n// Example: {\"error\":{\"grpcCode\":2,\"httpCode\":500,\"message\":\"...\",\"httpStatus\":\"text...\"}}.\ntype GRPCGatewayError struct {\n\tError                *GRPCGatewayError_GRPCGatewayErrorDetails `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}                                  `json:\"-\"`\n\tXXX_unrecognized     []byte                                    `json:\"-\"`\n\tXXX_sizecache        int32                                     `json:\"-\"`\n}\n\nfunc (m *GRPCGatewayError) Reset()         { *m = GRPCGatewayError{} }\nfunc (m *GRPCGatewayError) String() string { return proto.CompactTextString(m) }\nfunc (*GRPCGatewayError) ProtoMessage()    {}\nfunc (*GRPCGatewayError) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{8}\n}\nfunc (m *GRPCGatewayError) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GRPCGatewayError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GRPCGatewayError.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GRPCGatewayError) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GRPCGatewayError.Merge(m, src)\n}\nfunc (m *GRPCGatewayError) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GRPCGatewayError) XXX_DiscardUnknown() {\n\txxx_messageInfo_GRPCGatewayError.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GRPCGatewayError proto.InternalMessageInfo\n\nfunc (m *GRPCGatewayError) GetError() *GRPCGatewayError_GRPCGatewayErrorDetails {\n\tif m != nil {\n\t\treturn m.Error\n\t}\n\treturn nil\n}\n\ntype GRPCGatewayError_GRPCGatewayErrorDetails struct {\n\tGrpcCode             int32    `protobuf:\"varint,1,opt,name=grpcCode,proto3\" json:\"grpcCode,omitempty\"`\n\tHttpCode             int32    `protobuf:\"varint,2,opt,name=httpCode,proto3\" json:\"httpCode,omitempty\"`\n\tMessage              string   `protobuf:\"bytes,3,opt,name=message,proto3\" json:\"message,omitempty\"`\n\tHttpStatus           string   `protobuf:\"bytes,4,opt,name=httpStatus,proto3\" json:\"httpStatus,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) Reset() {\n\t*m = GRPCGatewayError_GRPCGatewayErrorDetails{}\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) String() string { return proto.CompactTextString(m) }\nfunc (*GRPCGatewayError_GRPCGatewayErrorDetails) ProtoMessage()    {}\nfunc (*GRPCGatewayError_GRPCGatewayErrorDetails) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{8, 0}\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GRPCGatewayError_GRPCGatewayErrorDetails.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GRPCGatewayError_GRPCGatewayErrorDetails.Merge(m, src)\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) XXX_DiscardUnknown() {\n\txxx_messageInfo_GRPCGatewayError_GRPCGatewayErrorDetails.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GRPCGatewayError_GRPCGatewayErrorDetails proto.InternalMessageInfo\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) GetGrpcCode() int32 {\n\tif m != nil {\n\t\treturn m.GrpcCode\n\t}\n\treturn 0\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) GetHttpCode() int32 {\n\tif m != nil {\n\t\treturn m.HttpCode\n\t}\n\treturn 0\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) GetMessage() string {\n\tif m != nil {\n\t\treturn m.Message\n\t}\n\treturn \"\"\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) GetHttpStatus() string {\n\tif m != nil {\n\t\treturn m.HttpStatus\n\t}\n\treturn \"\"\n}\n\n// GRPCGatewayWrapper wraps streaming responses from GetTrace/FindTraces for HTTP.\n// Today there is always only one response because internally the HTTP server gets\n// data from QueryService that does not support multiple responses. But in the\n// future the server may return multiple responeses using Transfer-Encoding: chunked.\n// In case of errors, GRPCGatewayError above is used.\n//\n// Example:\n//     {\"result\": {\"resourceSpans\": ...}}\n//\n// See https://github.com/grpc-ecosystem/grpc-gateway/issues/2189\n//\ntype GRPCGatewayWrapper struct {\n\tResult               *v1.TracesData `protobuf:\"bytes,1,opt,name=result,proto3\" json:\"result,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}       `json:\"-\"`\n\tXXX_unrecognized     []byte         `json:\"-\"`\n\tXXX_sizecache        int32          `json:\"-\"`\n}\n\nfunc (m *GRPCGatewayWrapper) Reset()         { *m = GRPCGatewayWrapper{} }\nfunc (m *GRPCGatewayWrapper) String() string { return proto.CompactTextString(m) }\nfunc (*GRPCGatewayWrapper) ProtoMessage()    {}\nfunc (*GRPCGatewayWrapper) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_5fcb6756dc1afb8d, []int{9}\n}\nfunc (m *GRPCGatewayWrapper) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GRPCGatewayWrapper) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GRPCGatewayWrapper.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GRPCGatewayWrapper) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GRPCGatewayWrapper.Merge(m, src)\n}\nfunc (m *GRPCGatewayWrapper) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GRPCGatewayWrapper) XXX_DiscardUnknown() {\n\txxx_messageInfo_GRPCGatewayWrapper.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GRPCGatewayWrapper proto.InternalMessageInfo\n\nfunc (m *GRPCGatewayWrapper) GetResult() *v1.TracesData {\n\tif m != nil {\n\t\treturn m.Result\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*GetTraceRequest)(nil), \"jaeger.api_v3.GetTraceRequest\")\n\tproto.RegisterType((*TraceQueryParameters)(nil), \"jaeger.api_v3.TraceQueryParameters\")\n\tproto.RegisterMapType((map[string]string)(nil), \"jaeger.api_v3.TraceQueryParameters.AttributesEntry\")\n\tproto.RegisterType((*FindTracesRequest)(nil), \"jaeger.api_v3.FindTracesRequest\")\n\tproto.RegisterType((*GetServicesRequest)(nil), \"jaeger.api_v3.GetServicesRequest\")\n\tproto.RegisterType((*GetServicesResponse)(nil), \"jaeger.api_v3.GetServicesResponse\")\n\tproto.RegisterType((*GetOperationsRequest)(nil), \"jaeger.api_v3.GetOperationsRequest\")\n\tproto.RegisterType((*Operation)(nil), \"jaeger.api_v3.Operation\")\n\tproto.RegisterType((*GetOperationsResponse)(nil), \"jaeger.api_v3.GetOperationsResponse\")\n\tproto.RegisterType((*GRPCGatewayError)(nil), \"jaeger.api_v3.GRPCGatewayError\")\n\tproto.RegisterType((*GRPCGatewayError_GRPCGatewayErrorDetails)(nil), \"jaeger.api_v3.GRPCGatewayError.GRPCGatewayErrorDetails\")\n\tproto.RegisterType((*GRPCGatewayWrapper)(nil), \"jaeger.api_v3.GRPCGatewayWrapper\")\n}\n\nfunc init() { proto.RegisterFile(\"query_service.proto\", fileDescriptor_5fcb6756dc1afb8d) }\n\nvar fileDescriptor_5fcb6756dc1afb8d = []byte{\n\t// 871 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xdd, 0x72, 0xdb, 0x44,\n\t0x14, 0x8e, 0xec, 0x38, 0xb1, 0x8e, 0x93, 0xb6, 0x6c, 0xcd, 0x54, 0x15, 0x83, 0xe3, 0xaa, 0x30,\n\t0xe3, 0x1b, 0x64, 0xe2, 0x5c, 0x50, 0x18, 0x18, 0xa0, 0x49, 0xeb, 0x01, 0x26, 0xa5, 0x55, 0x3a,\n\t0x85, 0x61, 0x3a, 0xa3, 0xd9, 0x44, 0x07, 0x47, 0xd4, 0x96, 0xd4, 0xdd, 0x95, 0x63, 0x3f, 0x03,\n\t0x37, 0x5c, 0xf2, 0x48, 0xbd, 0x84, 0x17, 0x28, 0x4c, 0x5e, 0x80, 0x17, 0xe0, 0x82, 0xd9, 0x1f,\n\t0xa9, 0xb6, 0x4c, 0x33, 0x69, 0xaf, 0xbc, 0xe7, 0xec, 0x77, 0x3e, 0x9f, 0x9f, 0xef, 0xac, 0xe0,\n\t0xfa, 0xf3, 0x1c, 0xd9, 0x3c, 0xe4, 0xc8, 0xa6, 0xf1, 0x09, 0xfa, 0x19, 0x4b, 0x45, 0x4a, 0xb6,\n\t0x7f, 0xa1, 0x38, 0x42, 0xe6, 0xd3, 0x2c, 0x0e, 0xa7, 0x7b, 0x6e, 0x67, 0x94, 0xa6, 0xa3, 0x31,\n\t0xf6, 0xd5, 0xe5, 0x71, 0xfe, 0x73, 0x3f, 0xca, 0x19, 0x15, 0x71, 0x9a, 0x68, 0xb8, 0xdb, 0x1e,\n\t0xa5, 0xa3, 0x54, 0x1d, 0xfb, 0xf2, 0x64, 0xbc, 0x3b, 0xd5, 0x28, 0x11, 0x4f, 0x90, 0x0b, 0x3a,\n\t0xc9, 0x0c, 0xa0, 0x97, 0x66, 0x98, 0x08, 0x1c, 0xe3, 0x04, 0x05, 0x9b, 0x6b, 0x5c, 0x5f, 0x30,\n\t0x7a, 0x82, 0xfd, 0xe9, 0xae, 0x3e, 0x68, 0xa4, 0xf7, 0xa7, 0x05, 0x57, 0x87, 0x28, 0x1e, 0x4b,\n\t0x57, 0x80, 0xcf, 0x73, 0xe4, 0x82, 0xdc, 0x84, 0xa6, 0x82, 0x84, 0x71, 0xe4, 0x58, 0x5d, 0xab,\n\t0x67, 0x07, 0x9b, 0xca, 0xfe, 0x26, 0x22, 0xfb, 0x00, 0x5c, 0x50, 0x26, 0x42, 0xf9, 0x8f, 0x4e,\n\t0xad, 0x6b, 0xf5, 0x5a, 0x03, 0xd7, 0xd7, 0xe9, 0xf8, 0x45, 0x3a, 0xfe, 0xe3, 0x22, 0x9d, 0xbb,\n\t0xcd, 0x17, 0x2f, 0x77, 0xd6, 0x7e, 0xfb, 0x6b, 0xc7, 0x0a, 0x6c, 0x15, 0x27, 0x6f, 0xc8, 0x97,\n\t0xd0, 0xc4, 0x24, 0xd2, 0x14, 0xf5, 0x37, 0xa0, 0xd8, 0xc4, 0x24, 0x52, 0x04, 0xef, 0x03, 0x30,\n\t0x7a, 0x16, 0xaa, 0xa4, 0xb8, 0xb3, 0xde, 0xb5, 0x7a, 0xcd, 0xc0, 0x66, 0xf4, 0x4c, 0x55, 0xc1,\n\t0xbd, 0x97, 0xeb, 0xd0, 0x56, 0xc7, 0x47, 0x72, 0x00, 0x0f, 0x29, 0xa3, 0x13, 0x14, 0xc8, 0x38,\n\t0xb9, 0x05, 0x5b, 0x66, 0x1a, 0x61, 0x42, 0x27, 0x68, 0x8a, 0x6b, 0x19, 0xdf, 0x03, 0x3a, 0x41,\n\t0xf2, 0x21, 0x5c, 0x49, 0x33, 0xd4, 0x33, 0xd0, 0xa0, 0x9a, 0x02, 0x6d, 0x97, 0x5e, 0x05, 0x3b,\n\t0x02, 0xa0, 0x42, 0xb0, 0xf8, 0x38, 0x17, 0xc8, 0x9d, 0x7a, 0xb7, 0xde, 0x6b, 0x0d, 0xf6, 0xfc,\n\t0xa5, 0xd9, 0xfa, 0xff, 0x97, 0x82, 0xff, 0x75, 0x19, 0x75, 0x2f, 0x11, 0x6c, 0x1e, 0x2c, 0xd0,\n\t0x90, 0x6f, 0xe1, 0xca, 0xab, 0xe6, 0x86, 0x93, 0x38, 0x51, 0xa5, 0x5d, 0xb6, 0x3b, 0x5b, 0x65,\n\t0x83, 0x0f, 0xe3, 0xa4, 0xca, 0x45, 0x67, 0x4e, 0xe3, 0xed, 0xb8, 0xe8, 0x8c, 0xdc, 0x87, 0xad,\n\t0x42, 0x96, 0x2a, 0xab, 0x0d, 0xc5, 0x74, 0x73, 0x85, 0xe9, 0xc0, 0x80, 0x34, 0xd1, 0xef, 0x92,\n\t0xa8, 0x55, 0x04, 0xca, 0x9c, 0x96, 0x78, 0xe8, 0xcc, 0xd9, 0x7c, 0x1b, 0x1e, 0x3a, 0xd3, 0x63,\n\t0xa4, 0xec, 0xe4, 0x34, 0x8c, 0x30, 0x13, 0xa7, 0x4e, 0xb3, 0x6b, 0xf5, 0x1a, 0x72, 0x8c, 0xd2,\n\t0x77, 0x20, 0x5d, 0x15, 0x85, 0xd8, 0x15, 0x85, 0xb8, 0x5f, 0xc0, 0xd5, 0xca, 0x20, 0xc8, 0x35,\n\t0xa8, 0x3f, 0xc3, 0xb9, 0x91, 0x84, 0x3c, 0x92, 0x36, 0x34, 0xa6, 0x74, 0x9c, 0x17, 0x0a, 0xd0,\n\t0xc6, 0x67, 0xb5, 0x3b, 0x96, 0xf7, 0x00, 0xde, 0xb9, 0x1f, 0x27, 0x91, 0x26, 0x2b, 0xb6, 0xe6,\n\t0x53, 0x68, 0xa8, 0x85, 0x57, 0x14, 0xad, 0xc1, 0xed, 0x4b, 0xa8, 0x21, 0xd0, 0x11, 0x5e, 0x1b,\n\t0xc8, 0x10, 0xc5, 0x91, 0x96, 0x61, 0x41, 0xe8, 0xed, 0xc2, 0xf5, 0x25, 0x2f, 0xcf, 0xd2, 0x84,\n\t0x23, 0x71, 0xa1, 0x69, 0x04, 0xcb, 0x1d, 0xab, 0x5b, 0xef, 0xd9, 0x41, 0x69, 0x7b, 0x87, 0xd0,\n\t0x1e, 0xa2, 0xf8, 0xbe, 0x90, 0x6a, 0x99, 0x9b, 0x03, 0x9b, 0x06, 0x53, 0x2c, 0xb4, 0x31, 0xc9,\n\t0x7b, 0x60, 0xf3, 0x8c, 0x26, 0xe1, 0xb3, 0x38, 0x89, 0x4c, 0xa1, 0x4d, 0xe9, 0xf8, 0x2e, 0x4e,\n\t0x22, 0xef, 0x73, 0xb0, 0x4b, 0x2e, 0x42, 0x60, 0x7d, 0x61, 0x69, 0xd4, 0xf9, 0xe2, 0xe8, 0x47,\n\t0xf0, 0x6e, 0x25, 0x19, 0x53, 0xc1, 0x1d, 0x80, 0x72, 0x9b, 0x74, 0x0d, 0xad, 0x81, 0x53, 0x69,\n\t0x57, 0x19, 0x16, 0x2c, 0x60, 0xbd, 0x7f, 0x2c, 0xb8, 0x36, 0x0c, 0x1e, 0xee, 0x0f, 0xa9, 0xc0,\n\t0x33, 0x3a, 0xbf, 0xc7, 0x58, 0xca, 0xc8, 0x21, 0x34, 0x50, 0x1e, 0x4c, 0xe3, 0x3f, 0xa9, 0x30,\n\t0x55, 0xf1, 0x2b, 0x8e, 0x03, 0x14, 0x34, 0x1e, 0xf3, 0x40, 0xb3, 0xb8, 0xbf, 0x5a, 0x70, 0xe3,\n\t0x35, 0x10, 0xd9, 0xfb, 0x11, 0xcb, 0x4e, 0xf6, 0xd3, 0x48, 0xf7, 0xa1, 0x11, 0x94, 0xb6, 0xbc,\n\t0x3b, 0x15, 0x22, 0x53, 0x77, 0x35, 0x7d, 0x57, 0xd8, 0xb2, 0xff, 0x13, 0xe4, 0x9c, 0x8e, 0xf4,\n\t0x83, 0x67, 0x07, 0x85, 0x49, 0x3a, 0x00, 0x12, 0x75, 0x24, 0xa8, 0xc8, 0xf5, 0x53, 0x66, 0x07,\n\t0x0b, 0x1e, 0xef, 0x09, 0x90, 0x85, 0x64, 0x7e, 0x60, 0x34, 0xcb, 0x90, 0x91, 0xaf, 0x60, 0x83,\n\t0x21, 0xcf, 0xc7, 0xc2, 0xd4, 0xdc, 0xf3, 0x97, 0x1e, 0x7c, 0xbd, 0x4a, 0xbe, 0x7e, 0xe7, 0xa7,\n\t0xbb, 0x5a, 0x7b, 0xfc, 0x80, 0x0a, 0x1a, 0x98, 0xb8, 0xc1, 0xbf, 0x35, 0xd8, 0x52, 0x6a, 0x34,\n\t0xfa, 0x22, 0x3f, 0x42, 0xb3, 0xf8, 0x0e, 0x90, 0x4e, 0xb5, 0x85, 0xcb, 0x1f, 0x08, 0xf7, 0xd2,\n\t0x7f, 0xe7, 0xad, 0x7d, 0x6c, 0x91, 0xa7, 0x00, 0xaf, 0xb6, 0x85, 0x74, 0x2b, 0xdc, 0x2b, 0x8b,\n\t0xf4, 0x86, 0xec, 0x4f, 0xa0, 0xb5, 0xb0, 0x25, 0xe4, 0xd6, 0x6a, 0xea, 0x95, 0xbd, 0x72, 0xbd,\n\t0x8b, 0x20, 0x5a, 0xa2, 0xde, 0x1a, 0x79, 0x0a, 0xdb, 0x4b, 0xea, 0x25, 0xb7, 0x57, 0xc3, 0x56,\n\t0x16, 0xcd, 0xfd, 0xe0, 0x62, 0x50, 0xc1, 0x7e, 0xf7, 0xa3, 0x17, 0xe7, 0x1d, 0xeb, 0x8f, 0xf3,\n\t0x8e, 0xf5, 0xf7, 0x79, 0xc7, 0x82, 0x1b, 0x71, 0x6a, 0xe2, 0x64, 0x95, 0x71, 0x32, 0x32, 0xe1,\n\t0x3f, 0x6d, 0xe8, 0xdf, 0xe3, 0x0d, 0xd5, 0x83, 0xbd, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x1f,\n\t0x1c, 0x84, 0x3f, 0x53, 0x08, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// QueryServiceClient is the client API for QueryService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype QueryServiceClient interface {\n\t// GetTrace returns a single trace.\n\t// Note that the JSON response over HTTP is wrapped into result envelope \"{\"result\": ...}\"\n\t// It means that the JSON response cannot be directly unmarshalled using JSONPb.\n\t// This can be fixed by first parsing into user-defined envelope with standard JSON library\n\t// or string manipulation to remove the envelope. Alternatively generate objects using OpenAPI.\n\tGetTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (QueryService_GetTraceClient, error)\n\t// FindTraces searches for traces.\n\t// See GetTrace for JSON unmarshalling.\n\tFindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (QueryService_FindTracesClient, error)\n\t// GetServices returns service names.\n\tGetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error)\n\t// GetOperations returns operation names.\n\tGetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error)\n}\n\ntype queryServiceClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewQueryServiceClient(cc *grpc.ClientConn) QueryServiceClient {\n\treturn &queryServiceClient{cc}\n}\n\nfunc (c *queryServiceClient) GetTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (QueryService_GetTraceClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_QueryService_serviceDesc.Streams[0], \"/jaeger.api_v3.QueryService/GetTrace\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &queryServiceGetTraceClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype QueryService_GetTraceClient interface {\n\tRecv() (*v1.TracesData, error)\n\tgrpc.ClientStream\n}\n\ntype queryServiceGetTraceClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *queryServiceGetTraceClient) Recv() (*v1.TracesData, error) {\n\tm := new(v1.TracesData)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *queryServiceClient) FindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (QueryService_FindTracesClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_QueryService_serviceDesc.Streams[1], \"/jaeger.api_v3.QueryService/FindTraces\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &queryServiceFindTracesClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype QueryService_FindTracesClient interface {\n\tRecv() (*v1.TracesData, error)\n\tgrpc.ClientStream\n}\n\ntype queryServiceFindTracesClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *queryServiceFindTracesClient) Recv() (*v1.TracesData, error) {\n\tm := new(v1.TracesData)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *queryServiceClient) GetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error) {\n\tout := new(GetServicesResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.api_v3.QueryService/GetServices\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *queryServiceClient) GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error) {\n\tout := new(GetOperationsResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.api_v3.QueryService/GetOperations\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// QueryServiceServer is the server API for QueryService service.\ntype QueryServiceServer interface {\n\t// GetTrace returns a single trace.\n\t// Note that the JSON response over HTTP is wrapped into result envelope \"{\"result\": ...}\"\n\t// It means that the JSON response cannot be directly unmarshalled using JSONPb.\n\t// This can be fixed by first parsing into user-defined envelope with standard JSON library\n\t// or string manipulation to remove the envelope. Alternatively generate objects using OpenAPI.\n\tGetTrace(*GetTraceRequest, QueryService_GetTraceServer) error\n\t// FindTraces searches for traces.\n\t// See GetTrace for JSON unmarshalling.\n\tFindTraces(*FindTracesRequest, QueryService_FindTracesServer) error\n\t// GetServices returns service names.\n\tGetServices(context.Context, *GetServicesRequest) (*GetServicesResponse, error)\n\t// GetOperations returns operation names.\n\tGetOperations(context.Context, *GetOperationsRequest) (*GetOperationsResponse, error)\n}\n\n// UnimplementedQueryServiceServer can be embedded to have forward compatible implementations.\ntype UnimplementedQueryServiceServer struct {\n}\n\nfunc (*UnimplementedQueryServiceServer) GetTrace(req *GetTraceRequest, srv QueryService_GetTraceServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method GetTrace not implemented\")\n}\nfunc (*UnimplementedQueryServiceServer) FindTraces(req *FindTracesRequest, srv QueryService_FindTracesServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method FindTraces not implemented\")\n}\nfunc (*UnimplementedQueryServiceServer) GetServices(ctx context.Context, req *GetServicesRequest) (*GetServicesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetServices not implemented\")\n}\nfunc (*UnimplementedQueryServiceServer) GetOperations(ctx context.Context, req *GetOperationsRequest) (*GetOperationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetOperations not implemented\")\n}\n\nfunc RegisterQueryServiceServer(s *grpc.Server, srv QueryServiceServer) {\n\ts.RegisterService(&_QueryService_serviceDesc, srv)\n}\n\nfunc _QueryService_GetTrace_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(GetTraceRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(QueryServiceServer).GetTrace(m, &queryServiceGetTraceServer{stream})\n}\n\ntype QueryService_GetTraceServer interface {\n\tSend(*v1.TracesData) error\n\tgrpc.ServerStream\n}\n\ntype queryServiceGetTraceServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *queryServiceGetTraceServer) Send(m *v1.TracesData) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _QueryService_FindTraces_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(FindTracesRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(QueryServiceServer).FindTraces(m, &queryServiceFindTracesServer{stream})\n}\n\ntype QueryService_FindTracesServer interface {\n\tSend(*v1.TracesData) error\n\tgrpc.ServerStream\n}\n\ntype queryServiceFindTracesServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *queryServiceFindTracesServer) Send(m *v1.TracesData) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _QueryService_GetServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetServicesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(QueryServiceServer).GetServices(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.api_v3.QueryService/GetServices\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(QueryServiceServer).GetServices(ctx, req.(*GetServicesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _QueryService_GetOperations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetOperationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(QueryServiceServer).GetOperations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.api_v3.QueryService/GetOperations\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(QueryServiceServer).GetOperations(ctx, req.(*GetOperationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _QueryService_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.api_v3.QueryService\",\n\tHandlerType: (*QueryServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetServices\",\n\t\t\tHandler:    _QueryService_GetServices_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetOperations\",\n\t\t\tHandler:    _QueryService_GetOperations_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"GetTrace\",\n\t\t\tHandler:       _QueryService_GetTrace_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"FindTraces\",\n\t\t\tHandler:       _QueryService_FindTraces_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"query_service.proto\",\n}\n\nfunc (m *GetTraceRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetTraceRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetTraceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.RawTraces {\n\t\ti--\n\t\tif m.RawTraces {\n\t\t\tdAtA[i] = 1\n\t\t} else {\n\t\t\tdAtA[i] = 0\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x20\n\t}\n\tn1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):])\n\tif err1 != nil {\n\t\treturn 0, err1\n\t}\n\ti -= n1\n\ti = encodeVarintQueryService(dAtA, i, uint64(n1))\n\ti--\n\tdAtA[i] = 0x1a\n\tn2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):])\n\tif err2 != nil {\n\t\treturn 0, err2\n\t}\n\ti -= n2\n\ti = encodeVarintQueryService(dAtA, i, uint64(n2))\n\ti--\n\tdAtA[i] = 0x12\n\tif len(m.TraceId) > 0 {\n\t\ti -= len(m.TraceId)\n\t\tcopy(dAtA[i:], m.TraceId)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.TraceId)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *TraceQueryParameters) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *TraceQueryParameters) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *TraceQueryParameters) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.RawTraces {\n\t\ti--\n\t\tif m.RawTraces {\n\t\t\tdAtA[i] = 1\n\t\t} else {\n\t\t\tdAtA[i] = 0\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x48\n\t}\n\tif m.SearchDepth != 0 {\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(m.SearchDepth))\n\t\ti--\n\t\tdAtA[i] = 0x40\n\t}\n\tn3, err3 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.DurationMax, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMax):])\n\tif err3 != nil {\n\t\treturn 0, err3\n\t}\n\ti -= n3\n\ti = encodeVarintQueryService(dAtA, i, uint64(n3))\n\ti--\n\tdAtA[i] = 0x3a\n\tn4, err4 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.DurationMin, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMin):])\n\tif err4 != nil {\n\t\treturn 0, err4\n\t}\n\ti -= n4\n\ti = encodeVarintQueryService(dAtA, i, uint64(n4))\n\ti--\n\tdAtA[i] = 0x32\n\tn5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTimeMax, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMax):])\n\tif err5 != nil {\n\t\treturn 0, err5\n\t}\n\ti -= n5\n\ti = encodeVarintQueryService(dAtA, i, uint64(n5))\n\ti--\n\tdAtA[i] = 0x2a\n\tn6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTimeMin, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMin):])\n\tif err6 != nil {\n\t\treturn 0, err6\n\t}\n\ti -= n6\n\ti = encodeVarintQueryService(dAtA, i, uint64(n6))\n\ti--\n\tdAtA[i] = 0x22\n\tif len(m.Attributes) > 0 {\n\t\tfor k := range m.Attributes {\n\t\t\tv := m.Attributes[k]\n\t\t\tbaseI := i\n\t\t\ti -= len(v)\n\t\t\tcopy(dAtA[i:], v)\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(v)))\n\t\t\ti--\n\t\t\tdAtA[i] = 0x12\n\t\t\ti -= len(k)\n\t\t\tcopy(dAtA[i:], k)\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(k)))\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(baseI-i))\n\t\t\ti--\n\t\t\tdAtA[i] = 0x1a\n\t\t}\n\t}\n\tif len(m.OperationName) > 0 {\n\t\ti -= len(m.OperationName)\n\t\tcopy(dAtA[i:], m.OperationName)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.OperationName)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.ServiceName) > 0 {\n\t\ti -= len(m.ServiceName)\n\t\tcopy(dAtA[i:], m.ServiceName)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.ServiceName)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FindTracesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FindTracesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FindTracesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Query != nil {\n\t\t{\n\t\t\tsize, err := m.Query.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetServicesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetServicesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetServicesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetServicesResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetServicesResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetServicesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Services) > 0 {\n\t\tfor iNdEx := len(m.Services) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\ti -= len(m.Services[iNdEx])\n\t\t\tcopy(dAtA[i:], m.Services[iNdEx])\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.Services[iNdEx])))\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetOperationsRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetOperationsRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetOperationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.SpanKind) > 0 {\n\t\ti -= len(m.SpanKind)\n\t\tcopy(dAtA[i:], m.SpanKind)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.SpanKind)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Service) > 0 {\n\t\ti -= len(m.Service)\n\t\tcopy(dAtA[i:], m.Service)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.Service)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Operation) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Operation) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Operation) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.SpanKind) > 0 {\n\t\ti -= len(m.SpanKind)\n\t\tcopy(dAtA[i:], m.SpanKind)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.SpanKind)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Name) > 0 {\n\t\ti -= len(m.Name)\n\t\tcopy(dAtA[i:], m.Name)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.Name)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetOperationsResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetOperationsResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetOperationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Operations) > 0 {\n\t\tfor iNdEx := len(m.Operations) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Operations[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GRPCGatewayError) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GRPCGatewayError) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GRPCGatewayError) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Error != nil {\n\t\t{\n\t\t\tsize, err := m.Error.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.HttpStatus) > 0 {\n\t\ti -= len(m.HttpStatus)\n\t\tcopy(dAtA[i:], m.HttpStatus)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.HttpStatus)))\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\tif len(m.Message) > 0 {\n\t\ti -= len(m.Message)\n\t\tcopy(dAtA[i:], m.Message)\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(len(m.Message)))\n\t\ti--\n\t\tdAtA[i] = 0x1a\n\t}\n\tif m.HttpCode != 0 {\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(m.HttpCode))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.GrpcCode != 0 {\n\t\ti = encodeVarintQueryService(dAtA, i, uint64(m.GrpcCode))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GRPCGatewayWrapper) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GRPCGatewayWrapper) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GRPCGatewayWrapper) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Result != nil {\n\t\t{\n\t\t\tsize, err := m.Result.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintQueryService(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc encodeVarintQueryService(dAtA []byte, offset int, v uint64) int {\n\toffset -= sovQueryService(v)\n\tbase := offset\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn base\n}\nfunc (m *GetTraceRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.TraceId)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime)\n\tn += 1 + l + sovQueryService(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime)\n\tn += 1 + l + sovQueryService(uint64(l))\n\tif m.RawTraces {\n\t\tn += 2\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *TraceQueryParameters) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.ServiceName)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tl = len(m.OperationName)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif len(m.Attributes) > 0 {\n\t\tfor k, v := range m.Attributes {\n\t\t\t_ = k\n\t\t\t_ = v\n\t\t\tmapEntrySize := 1 + len(k) + sovQueryService(uint64(len(k))) + 1 + len(v) + sovQueryService(uint64(len(v)))\n\t\t\tn += mapEntrySize + 1 + sovQueryService(uint64(mapEntrySize))\n\t\t}\n\t}\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMin)\n\tn += 1 + l + sovQueryService(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMax)\n\tn += 1 + l + sovQueryService(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMin)\n\tn += 1 + l + sovQueryService(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMax)\n\tn += 1 + l + sovQueryService(uint64(l))\n\tif m.SearchDepth != 0 {\n\t\tn += 1 + sovQueryService(uint64(m.SearchDepth))\n\t}\n\tif m.RawTraces {\n\t\tn += 2\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FindTracesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Query != nil {\n\t\tl = m.Query.Size()\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetServicesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetServicesResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Services) > 0 {\n\t\tfor _, s := range m.Services {\n\t\t\tl = len(s)\n\t\t\tn += 1 + l + sovQueryService(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetOperationsRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Service)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tl = len(m.SpanKind)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Operation) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Name)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tl = len(m.SpanKind)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetOperationsResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Operations) > 0 {\n\t\tfor _, e := range m.Operations {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovQueryService(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GRPCGatewayError) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Error != nil {\n\t\tl = m.Error.Size()\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.GrpcCode != 0 {\n\t\tn += 1 + sovQueryService(uint64(m.GrpcCode))\n\t}\n\tif m.HttpCode != 0 {\n\t\tn += 1 + sovQueryService(uint64(m.HttpCode))\n\t}\n\tl = len(m.Message)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tl = len(m.HttpStatus)\n\tif l > 0 {\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GRPCGatewayWrapper) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Result != nil {\n\t\tl = m.Result.Size()\n\t\tn += 1 + l + sovQueryService(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc sovQueryService(x uint64) (n int) {\n\treturn (math_bits.Len64(x|1) + 6) / 7\n}\nfunc sozQueryService(x uint64) (n int) {\n\treturn sovQueryService(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *GetTraceRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetTraceRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetTraceRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field TraceId\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.TraceId = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field EndTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field RawTraces\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.RawTraces = bool(v != 0)\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *TraceQueryParameters) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: TraceQueryParameters: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: TraceQueryParameters: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ServiceName\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.ServiceName = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OperationName\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.OperationName = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Attributes\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Attributes == nil {\n\t\t\t\tm.Attributes = make(map[string]string)\n\t\t\t}\n\t\t\tvar mapkey string\n\t\t\tvar mapvalue string\n\t\t\tfor iNdEx < postIndex {\n\t\t\t\tentryPreIndex := iNdEx\n\t\t\t\tvar wire uint64\n\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t\t}\n\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\tiNdEx++\n\t\t\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfieldNum := int32(wire >> 3)\n\t\t\t\tif fieldNum == 1 {\n\t\t\t\t\tvar stringLenmapkey uint64\n\t\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t\t}\n\t\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\t\tiNdEx++\n\t\t\t\t\t\tstringLenmapkey |= uint64(b&0x7F) << shift\n\t\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tintStringLenmapkey := int(stringLenmapkey)\n\t\t\t\t\tif intStringLenmapkey < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t\t\t}\n\t\t\t\t\tpostStringIndexmapkey := iNdEx + intStringLenmapkey\n\t\t\t\t\tif postStringIndexmapkey < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t\t\t}\n\t\t\t\t\tif postStringIndexmapkey > l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tmapkey = string(dAtA[iNdEx:postStringIndexmapkey])\n\t\t\t\t\tiNdEx = postStringIndexmapkey\n\t\t\t\t} else if fieldNum == 2 {\n\t\t\t\t\tvar stringLenmapvalue uint64\n\t\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t\t}\n\t\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\t\tiNdEx++\n\t\t\t\t\t\tstringLenmapvalue |= uint64(b&0x7F) << shift\n\t\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tintStringLenmapvalue := int(stringLenmapvalue)\n\t\t\t\t\tif intStringLenmapvalue < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t\t\t}\n\t\t\t\t\tpostStringIndexmapvalue := iNdEx + intStringLenmapvalue\n\t\t\t\t\tif postStringIndexmapvalue < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t\t\t}\n\t\t\t\t\tif postStringIndexmapvalue > l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tmapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])\n\t\t\t\t\tiNdEx = postStringIndexmapvalue\n\t\t\t\t} else {\n\t\t\t\t\tiNdEx = entryPreIndex\n\t\t\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t\t\t}\n\t\t\t\t\tif (iNdEx + skippy) > postIndex {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tiNdEx += skippy\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Attributes[mapkey] = mapvalue\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTimeMin\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTimeMin, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTimeMax\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTimeMax, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DurationMin\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.DurationMin, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DurationMax\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.DurationMax, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 8:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SearchDepth\", wireType)\n\t\t\t}\n\t\t\tm.SearchDepth = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.SearchDepth |= int32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 9:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field RawTraces\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.RawTraces = bool(v != 0)\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FindTracesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FindTracesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FindTracesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Query\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Query == nil {\n\t\t\t\tm.Query = &TraceQueryParameters{}\n\t\t\t}\n\t\t\tif err := m.Query.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetServicesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetServicesResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Services\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Services = append(m.Services, string(dAtA[iNdEx:postIndex]))\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetOperationsRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Service\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Service = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SpanKind\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.SpanKind = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Operation) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Operation: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Operation: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Name\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Name = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SpanKind\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.SpanKind = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetOperationsResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Operations\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Operations = append(m.Operations, &Operation{})\n\t\t\tif err := m.Operations[len(m.Operations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GRPCGatewayError) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GRPCGatewayError: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GRPCGatewayError: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Error\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Error == nil {\n\t\t\t\tm.Error = &GRPCGatewayError_GRPCGatewayErrorDetails{}\n\t\t\t}\n\t\t\tif err := m.Error.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GRPCGatewayError_GRPCGatewayErrorDetails) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GRPCGatewayErrorDetails: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GRPCGatewayErrorDetails: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field GrpcCode\", wireType)\n\t\t\t}\n\t\t\tm.GrpcCode = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.GrpcCode |= int32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field HttpCode\", wireType)\n\t\t\t}\n\t\t\tm.HttpCode = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.HttpCode |= int32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Message\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Message = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field HttpStatus\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.HttpStatus = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GRPCGatewayWrapper) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GRPCGatewayWrapper: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GRPCGatewayWrapper: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Result\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Result == nil {\n\t\t\t\tm.Result = &v1.TracesData{}\n\t\t\t}\n\t\t\tif err := m.Result.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipQueryService(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipQueryService(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tdepth := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowQueryService\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowQueryService\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthQueryService\n\t\t\t}\n\t\t\tiNdEx += length\n\t\tcase 3:\n\t\t\tdepth++\n\t\tcase 4:\n\t\t\tif depth == 0 {\n\t\t\t\treturn 0, ErrUnexpectedEndOfGroupQueryService\n\t\t\t}\n\t\t\tdepth--\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t\tif iNdEx < 0 {\n\t\t\treturn 0, ErrInvalidLengthQueryService\n\t\t}\n\t\tif depth == 0 {\n\t\t\treturn iNdEx, nil\n\t\t}\n\t}\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nvar (\n\tErrInvalidLengthQueryService        = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowQueryService          = fmt.Errorf(\"proto: integer overflow\")\n\tErrUnexpectedEndOfGroupQueryService = fmt.Errorf(\"proto: unexpected end of group\")\n)\n"
  },
  {
    "path": "internal/proto/metrics/README.md",
    "content": "# Metrics Query Service\n\nDefines the MetricsQueryService's set of APIs along with required data models.\n\n## Overview\n\nContained in this directory are a set of shared Protobuf data model definitions from\nhttps://github.com/OpenObservability/OpenMetrics, namely:\n\n- openmetrics.proto: OpenMetrics' data model.\n\nThe reasons for adopting OpenMetrics' data model over OpenTelemetry's are:\n\n- OpenTelemetry is still changing, demonstrated by a recent copy of the data model being taken which became outdated after a few weeks.\n- OpenTelemetry is supposedly fully compatible with OpenMetrics by the end of the year.\n- OpenMetrics is the de-factor Prometheus format used by many backends already.\n- OpenMetrics is stable.\n- OpenTelemetry will support OpenTelemetry <-> OpenMetrics conversion in the future,\n  so we would still be able to implement support for OpenTelemetry-native backends.\n\nImporting data models directly from the OpenObservability/OpenMetrics github repo (via a submodule)\nwas considered and explored; however, without custom marshaling enabled, which is required for sending\nimported message types over the wire, errors such as the following result:\n\n    `panic: invalid Go type v1.Metric for field jaeger.api_v2.GetMetricsResponse.Metrics`\n\nEnabling gogoproto's custom Marshal and Unmarshal methods to address the above issue result\nin compilation errors from the generated code as the referenced protobuf definition does not have\ngogoproto.marshaler_all, gogoproto.unmarshaler_all, etc. enabled.\n\nMoreover, if direct imports of other repositories were possible, it would mean importing and generating code for\ntransitive dependencies not required by Jaeger leading to longer build times, and potentially larger container\nimage sizes.\n\nGiven the aforementioned limitations, selectively copying necessary messages and enums allow for:\n\n- Marshaling and unmarshaling of externally defined custom data models such as those from OpenMetrics.\n- Using Gogoproto's custom un/marshalers takes advantage of [reportedly faster marshaling and\n  unmarshaling](https://github.com/cockroachdb/gogoproto/blob/master/extensions.md).\n- Avoiding unwanted dependencies leading to simpler proto definitions,\n  faster build times and smaller image sizes.\n\nThe key trade-offs are:\n\n- Synchronizing with the original source proto definition.\n  - It is anticipated that the maintenance effort to synchronize data models will be minimal considering\n    there is no direct dependency between Jaeger and OpenTelemetry in the context of querying metrics,\n    with exception to `SpanKind`, and the existing data model more than satisfies existing metrics querying requirements.\n\n    The OpenTelemetry metrics data model primarily serves as a carrier of metrics data, rather than a protocol\n    of communication between Jaeger and OpenTelemetry components.\n"
  },
  {
    "path": "internal/proto/metrics/openmetrics.proto",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// This file is a copy of https://github.com/OpenObservability/OpenMetrics/blob/v1.0.0/proto/openmetrics_data_model.proto,\n// with the following additions (see README.md for more details):\n//\n//  * Add import to gogoproto/gogo.proto.\n//  * Add options defining the per-language generated code's packages.\n//  * Add options enabling gogoproto un/marshal, required for sending imported message types over the wire.\n\nsyntax=\"proto3\";\n\n// The OpenMetrics protobuf schema which defines the protobuf wire format.\n// Ensure to interpret \"required\" as semantically required for a valid message.\n// All string fields MUST be UTF-8 encoded strings.\npackage jaeger.api_v2.metrics;\n\nimport \"google/protobuf/timestamp.proto\";\n// -- BEGIN ADDITIONS\nimport \"gogoproto/gogo.proto\";\n\noption go_package = \"metrics\";\noption java_package = \"io.jaegertracing.api_v2.metrics\";\n\n// Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md).\n// Enable custom Marshal method.\noption (gogoproto.marshaler_all) = true;\n// Enable custom Unmarshal method.\noption (gogoproto.unmarshaler_all) = true;\n// Enable custom Size method (Required by Marshal and Unmarshal).\noption (gogoproto.sizer_all) = true;\n// -- END ADDITIONS\n\n// The top-level container type that is encoded and sent over the wire.\nmessage MetricSet {\n  // Each MetricFamily has one or more MetricPoints for a single Metric.\n  repeated MetricFamily metric_families = 1;\n}\n\n// One or more Metrics for a single MetricFamily, where each Metric\n// has one or more MetricPoints.\nmessage MetricFamily {\n  // Required.\n  string name = 1;\n\n  // Optional.\n  MetricType type = 2;\n\n  // Optional.\n  string unit = 3;\n\n  // Optional.\n  string help = 4;\n\n  // Optional.\n  repeated Metric metrics = 5;\n}\n\n// The type of a Metric.\nenum MetricType {\n  // Unknown must use unknown MetricPoint values.\n  UNKNOWN = 0;\n  // Gauge must use gauge MetricPoint values.\n  GAUGE = 1;\n  // Counter must use counter MetricPoint values.\n  COUNTER = 2;\n  // State set must use state set MetricPoint values.\n  STATE_SET = 3;\n  // Info must use info MetricPoint values.\n  INFO = 4;\n  // Histogram must use histogram value MetricPoint values.\n  HISTOGRAM = 5;\n  // Gauge histogram must use histogram value MetricPoint values.\n  GAUGE_HISTOGRAM = 6;\n  // Summary quantiles must use summary value MetricPoint values.\n  SUMMARY = 7;\n}\n\n// A single metric with a unique set of labels within a metric family.\nmessage Metric {\n  // Optional.\n  repeated Label labels = 1;\n\n  // Optional.\n  repeated MetricPoint metric_points = 2;\n}\n\n// A name-value pair. These are used in multiple places: identifying\n// timeseries, value of INFO metrics, and exemplars in Histograms.\nmessage Label {\n  // Required.\n  string name = 1;\n\n  // Required.\n  string value = 2;\n}\n\n// A MetricPoint in a Metric.\nmessage MetricPoint {\n  // Required.\n  oneof value {\n    UnknownValue unknown_value = 1;\n    GaugeValue gauge_value = 2;\n    CounterValue counter_value = 3;\n    HistogramValue histogram_value = 4;\n    StateSetValue state_set_value = 5;\n    InfoValue info_value = 6;\n    SummaryValue summary_value = 7;\n  }\n\n  // Optional.\n  google.protobuf.Timestamp timestamp = 8;\n}\n\n// Value for UNKNOWN MetricPoint.\nmessage UnknownValue {\n  // Required.\n  oneof value {\n    double double_value = 1;\n    int64 int_value = 2;\n  }\n}\n\n// Value for GAUGE MetricPoint.\nmessage GaugeValue {\n  // Required.\n  oneof value {\n    double double_value = 1;\n    int64 int_value = 2;\n  }\n}\n\n// Value for COUNTER MetricPoint.\nmessage CounterValue {\n  // Required.\n  oneof total {\n    double double_value = 1;\n    uint64 int_value = 2;\n  }\n\n  // The time values began being collected for this counter.\n  // Optional.\n  google.protobuf.Timestamp created = 3;\n\n  // Optional.\n  Exemplar exemplar = 4;\n}\n\n// Value for HISTOGRAM or GAUGE_HISTOGRAM MetricPoint.\nmessage HistogramValue {\n  // Optional.\n  oneof sum {\n    double double_value = 1;\n    int64 int_value = 2;\n  }\n\n  // Optional.\n  uint64 count = 3;\n\n  // The time values began being collected for this histogram.\n  // Optional.\n  google.protobuf.Timestamp created = 4;\n\n  // Optional.\n  repeated Bucket buckets = 5;\n\n  // Bucket is the number of values for a bucket in the histogram\n  // with an optional exemplar.\n  message Bucket {\n    // Required.\n    uint64 count = 1;\n\n    // Optional.\n    double upper_bound = 2;\n\n    // Optional.\n    Exemplar exemplar = 3;\n  }\n}\n\nmessage Exemplar {\n  // Required.\n  double value = 1;\n\n  // Optional.\n  google.protobuf.Timestamp timestamp = 2;\n\n  // Labels are additional information about the exemplar value (e.g. trace id).\n  // Optional.\n  repeated Label label = 3;\n}\n\n// Value for STATE_SET MetricPoint.\nmessage StateSetValue {\n  // Optional.\n  repeated State states = 1;\n\n  message State {\n    // Required.\n    bool enabled = 1;\n\n    // Required.\n    string name = 2;\n  }\n}\n\n// Value for INFO MetricPoint.\nmessage InfoValue {\n  // Optional.\n  repeated Label info = 1;\n}\n\n// Value for SUMMARY MetricPoint.\nmessage SummaryValue {\n  // Optional.\n  oneof sum {\n    double double_value = 1;\n    int64 int_value = 2;\n  }\n\n  // Optional.\n  uint64 count = 3;\n\n  // The time sum and count values began being collected for this summary.\n  // Optional.\n  google.protobuf.Timestamp created = 4;\n\n  // Optional.\n  repeated Quantile quantile = 5;\n\n  message Quantile {\n    // Required.\n    double quantile = 1;\n\n    // Required.\n    double value = 2;\n  }\n}"
  },
  {
    "path": "internal/proto/metrics/otelspankind.proto",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Based on: https://github.com/open-telemetry/opentelemetry-proto/blob/v0.8.0/opentelemetry/proto/trace/v1/trace.proto\n\nsyntax=\"proto3\";\n\npackage jaeger.api_v2.metrics;\n\nimport \"gogoproto/gogo.proto\";\n\noption go_package = \"metrics\";\noption java_package = \"io.jaegertracing.api_v2.metrics\";\n\n// Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md).\n// Enable custom Marshal method.\noption (gogoproto.marshaler_all) = true;\n// Enable custom Unmarshal method.\noption (gogoproto.unmarshaler_all) = true;\n// Enable custom Size method (Required by Marshal and Unmarshal).\noption (gogoproto.sizer_all) = true;\n\n// SpanKind is the type of span. Can be used to specify additional relationships between spans\n// in addition to a parent/child relationship.\nenum SpanKind {\n  // Unspecified. Do NOT use as default.\n  // Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED.\n  SPAN_KIND_UNSPECIFIED = 0;\n\n  // Indicates that the span represents an internal operation within an application,\n  // as opposed to an operation happening at the boundaries. Default value.\n  SPAN_KIND_INTERNAL = 1;\n\n  // Indicates that the span covers server-side handling of an RPC or other\n  // remote network request.\n  SPAN_KIND_SERVER = 2;\n\n  // Indicates that the span describes a request to some remote service.\n  SPAN_KIND_CLIENT = 3;\n\n  // Indicates that the span describes a producer sending a message to a broker.\n  // Unlike CLIENT and SERVER, there is often no direct critical path latency relationship\n  // between producer and consumer spans. A PRODUCER span ends when the message was accepted\n  // by the broker while the logical processing of the message might span a much longer time.\n  SPAN_KIND_PRODUCER = 4;\n\n  // Indicates that the span describes consumer receiving a message from a broker.\n  // Like the PRODUCER kind, there is often no direct critical path latency relationship\n  // between producer and consumer spans.\n  SPAN_KIND_CONSUMER = 5;\n}\n"
  },
  {
    "path": "internal/proto-gen/.gitignore",
    "content": ".patched/\n"
  },
  {
    "path": "internal/proto-gen/api_v2/metrics/openmetrics.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: openmetrics.proto\n\n// The OpenMetrics protobuf schema which defines the protobuf wire format.\n// Ensure to interpret \"required\" as semantically required for a valid message.\n// All string fields MUST be UTF-8 encoded strings.\n\npackage metrics\n\nimport (\n\tencoding_binary \"encoding/binary\"\n\tfmt \"fmt\"\n\t_ \"github.com/gogo/protobuf/gogoproto\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\ttypes \"github.com/gogo/protobuf/types\"\n\tio \"io\"\n\tmath \"math\"\n\tmath_bits \"math/bits\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\n// The type of a Metric.\ntype MetricType int32\n\nconst (\n\t// Unknown must use unknown MetricPoint values.\n\tMetricType_UNKNOWN MetricType = 0\n\t// Gauge must use gauge MetricPoint values.\n\tMetricType_GAUGE MetricType = 1\n\t// Counter must use counter MetricPoint values.\n\tMetricType_COUNTER MetricType = 2\n\t// State set must use state set MetricPoint values.\n\tMetricType_STATE_SET MetricType = 3\n\t// Info must use info MetricPoint values.\n\tMetricType_INFO MetricType = 4\n\t// Histogram must use histogram value MetricPoint values.\n\tMetricType_HISTOGRAM MetricType = 5\n\t// Gauge histogram must use histogram value MetricPoint values.\n\tMetricType_GAUGE_HISTOGRAM MetricType = 6\n\t// Summary quantiles must use summary value MetricPoint values.\n\tMetricType_SUMMARY MetricType = 7\n)\n\nvar MetricType_name = map[int32]string{\n\t0: \"UNKNOWN\",\n\t1: \"GAUGE\",\n\t2: \"COUNTER\",\n\t3: \"STATE_SET\",\n\t4: \"INFO\",\n\t5: \"HISTOGRAM\",\n\t6: \"GAUGE_HISTOGRAM\",\n\t7: \"SUMMARY\",\n}\n\nvar MetricType_value = map[string]int32{\n\t\"UNKNOWN\":         0,\n\t\"GAUGE\":           1,\n\t\"COUNTER\":         2,\n\t\"STATE_SET\":       3,\n\t\"INFO\":            4,\n\t\"HISTOGRAM\":       5,\n\t\"GAUGE_HISTOGRAM\": 6,\n\t\"SUMMARY\":         7,\n}\n\nfunc (x MetricType) String() string {\n\treturn proto.EnumName(MetricType_name, int32(x))\n}\n\nfunc (MetricType) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{0}\n}\n\n// The top-level container type that is encoded and sent over the wire.\ntype MetricSet struct {\n\t// Each MetricFamily has one or more MetricPoints for a single Metric.\n\tMetricFamilies       []*MetricFamily `protobuf:\"bytes,1,rep,name=metric_families,json=metricFamilies,proto3\" json:\"metric_families,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *MetricSet) Reset()         { *m = MetricSet{} }\nfunc (m *MetricSet) String() string { return proto.CompactTextString(m) }\nfunc (*MetricSet) ProtoMessage()    {}\nfunc (*MetricSet) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{0}\n}\nfunc (m *MetricSet) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *MetricSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_MetricSet.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *MetricSet) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_MetricSet.Merge(m, src)\n}\nfunc (m *MetricSet) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *MetricSet) XXX_DiscardUnknown() {\n\txxx_messageInfo_MetricSet.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_MetricSet proto.InternalMessageInfo\n\nfunc (m *MetricSet) GetMetricFamilies() []*MetricFamily {\n\tif m != nil {\n\t\treturn m.MetricFamilies\n\t}\n\treturn nil\n}\n\n// One or more Metrics for a single MetricFamily, where each Metric\n// has one or more MetricPoints.\ntype MetricFamily struct {\n\t// Required.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Optional.\n\tType MetricType `protobuf:\"varint,2,opt,name=type,proto3,enum=jaeger.api_v2.metrics.MetricType\" json:\"type,omitempty\"`\n\t// Optional.\n\tUnit string `protobuf:\"bytes,3,opt,name=unit,proto3\" json:\"unit,omitempty\"`\n\t// Optional.\n\tHelp string `protobuf:\"bytes,4,opt,name=help,proto3\" json:\"help,omitempty\"`\n\t// Optional.\n\tMetrics              []*Metric `protobuf:\"bytes,5,rep,name=metrics,proto3\" json:\"metrics,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *MetricFamily) Reset()         { *m = MetricFamily{} }\nfunc (m *MetricFamily) String() string { return proto.CompactTextString(m) }\nfunc (*MetricFamily) ProtoMessage()    {}\nfunc (*MetricFamily) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{1}\n}\nfunc (m *MetricFamily) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *MetricFamily) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_MetricFamily.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *MetricFamily) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_MetricFamily.Merge(m, src)\n}\nfunc (m *MetricFamily) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *MetricFamily) XXX_DiscardUnknown() {\n\txxx_messageInfo_MetricFamily.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_MetricFamily proto.InternalMessageInfo\n\nfunc (m *MetricFamily) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *MetricFamily) GetType() MetricType {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn MetricType_UNKNOWN\n}\n\nfunc (m *MetricFamily) GetUnit() string {\n\tif m != nil {\n\t\treturn m.Unit\n\t}\n\treturn \"\"\n}\n\nfunc (m *MetricFamily) GetHelp() string {\n\tif m != nil {\n\t\treturn m.Help\n\t}\n\treturn \"\"\n}\n\nfunc (m *MetricFamily) GetMetrics() []*Metric {\n\tif m != nil {\n\t\treturn m.Metrics\n\t}\n\treturn nil\n}\n\n// A single metric with a unique set of labels within a metric family.\ntype Metric struct {\n\t// Optional.\n\tLabels []*Label `protobuf:\"bytes,1,rep,name=labels,proto3\" json:\"labels,omitempty\"`\n\t// Optional.\n\tMetricPoints         []*MetricPoint `protobuf:\"bytes,2,rep,name=metric_points,json=metricPoints,proto3\" json:\"metric_points,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}       `json:\"-\"`\n\tXXX_unrecognized     []byte         `json:\"-\"`\n\tXXX_sizecache        int32          `json:\"-\"`\n}\n\nfunc (m *Metric) Reset()         { *m = Metric{} }\nfunc (m *Metric) String() string { return proto.CompactTextString(m) }\nfunc (*Metric) ProtoMessage()    {}\nfunc (*Metric) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{2}\n}\nfunc (m *Metric) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Metric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Metric.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Metric) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Metric.Merge(m, src)\n}\nfunc (m *Metric) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Metric) XXX_DiscardUnknown() {\n\txxx_messageInfo_Metric.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Metric proto.InternalMessageInfo\n\nfunc (m *Metric) GetLabels() []*Label {\n\tif m != nil {\n\t\treturn m.Labels\n\t}\n\treturn nil\n}\n\nfunc (m *Metric) GetMetricPoints() []*MetricPoint {\n\tif m != nil {\n\t\treturn m.MetricPoints\n\t}\n\treturn nil\n}\n\n// A name-value pair. These are used in multiple places: identifying\n// timeseries, value of INFO metrics, and exemplars in Histograms.\ntype Label struct {\n\t// Required.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Required.\n\tValue                string   `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Label) Reset()         { *m = Label{} }\nfunc (m *Label) String() string { return proto.CompactTextString(m) }\nfunc (*Label) ProtoMessage()    {}\nfunc (*Label) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{3}\n}\nfunc (m *Label) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Label) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Label.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Label) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Label.Merge(m, src)\n}\nfunc (m *Label) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Label) XXX_DiscardUnknown() {\n\txxx_messageInfo_Label.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Label proto.InternalMessageInfo\n\nfunc (m *Label) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Label) GetValue() string {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn \"\"\n}\n\n// A MetricPoint in a Metric.\ntype MetricPoint struct {\n\t// Required.\n\t//\n\t// Types that are valid to be assigned to Value:\n\t//\t*MetricPoint_UnknownValue\n\t//\t*MetricPoint_GaugeValue\n\t//\t*MetricPoint_CounterValue\n\t//\t*MetricPoint_HistogramValue\n\t//\t*MetricPoint_StateSetValue\n\t//\t*MetricPoint_InfoValue\n\t//\t*MetricPoint_SummaryValue\n\tValue isMetricPoint_Value `protobuf_oneof:\"value\"`\n\t// Optional.\n\tTimestamp            *types.Timestamp `protobuf:\"bytes,8,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}         `json:\"-\"`\n\tXXX_unrecognized     []byte           `json:\"-\"`\n\tXXX_sizecache        int32            `json:\"-\"`\n}\n\nfunc (m *MetricPoint) Reset()         { *m = MetricPoint{} }\nfunc (m *MetricPoint) String() string { return proto.CompactTextString(m) }\nfunc (*MetricPoint) ProtoMessage()    {}\nfunc (*MetricPoint) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{4}\n}\nfunc (m *MetricPoint) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *MetricPoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_MetricPoint.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *MetricPoint) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_MetricPoint.Merge(m, src)\n}\nfunc (m *MetricPoint) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *MetricPoint) XXX_DiscardUnknown() {\n\txxx_messageInfo_MetricPoint.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_MetricPoint proto.InternalMessageInfo\n\ntype isMetricPoint_Value interface {\n\tisMetricPoint_Value()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype MetricPoint_UnknownValue struct {\n\tUnknownValue *UnknownValue `protobuf:\"bytes,1,opt,name=unknown_value,json=unknownValue,proto3,oneof\" json:\"unknown_value,omitempty\"`\n}\ntype MetricPoint_GaugeValue struct {\n\tGaugeValue *GaugeValue `protobuf:\"bytes,2,opt,name=gauge_value,json=gaugeValue,proto3,oneof\" json:\"gauge_value,omitempty\"`\n}\ntype MetricPoint_CounterValue struct {\n\tCounterValue *CounterValue `protobuf:\"bytes,3,opt,name=counter_value,json=counterValue,proto3,oneof\" json:\"counter_value,omitempty\"`\n}\ntype MetricPoint_HistogramValue struct {\n\tHistogramValue *HistogramValue `protobuf:\"bytes,4,opt,name=histogram_value,json=histogramValue,proto3,oneof\" json:\"histogram_value,omitempty\"`\n}\ntype MetricPoint_StateSetValue struct {\n\tStateSetValue *StateSetValue `protobuf:\"bytes,5,opt,name=state_set_value,json=stateSetValue,proto3,oneof\" json:\"state_set_value,omitempty\"`\n}\ntype MetricPoint_InfoValue struct {\n\tInfoValue *InfoValue `protobuf:\"bytes,6,opt,name=info_value,json=infoValue,proto3,oneof\" json:\"info_value,omitempty\"`\n}\ntype MetricPoint_SummaryValue struct {\n\tSummaryValue *SummaryValue `protobuf:\"bytes,7,opt,name=summary_value,json=summaryValue,proto3,oneof\" json:\"summary_value,omitempty\"`\n}\n\nfunc (*MetricPoint_UnknownValue) isMetricPoint_Value()   {}\nfunc (*MetricPoint_GaugeValue) isMetricPoint_Value()     {}\nfunc (*MetricPoint_CounterValue) isMetricPoint_Value()   {}\nfunc (*MetricPoint_HistogramValue) isMetricPoint_Value() {}\nfunc (*MetricPoint_StateSetValue) isMetricPoint_Value()  {}\nfunc (*MetricPoint_InfoValue) isMetricPoint_Value()      {}\nfunc (*MetricPoint_SummaryValue) isMetricPoint_Value()   {}\n\nfunc (m *MetricPoint) GetValue() isMetricPoint_Value {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetUnknownValue() *UnknownValue {\n\tif x, ok := m.GetValue().(*MetricPoint_UnknownValue); ok {\n\t\treturn x.UnknownValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetGaugeValue() *GaugeValue {\n\tif x, ok := m.GetValue().(*MetricPoint_GaugeValue); ok {\n\t\treturn x.GaugeValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetCounterValue() *CounterValue {\n\tif x, ok := m.GetValue().(*MetricPoint_CounterValue); ok {\n\t\treturn x.CounterValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetHistogramValue() *HistogramValue {\n\tif x, ok := m.GetValue().(*MetricPoint_HistogramValue); ok {\n\t\treturn x.HistogramValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetStateSetValue() *StateSetValue {\n\tif x, ok := m.GetValue().(*MetricPoint_StateSetValue); ok {\n\t\treturn x.StateSetValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetInfoValue() *InfoValue {\n\tif x, ok := m.GetValue().(*MetricPoint_InfoValue); ok {\n\t\treturn x.InfoValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetSummaryValue() *SummaryValue {\n\tif x, ok := m.GetValue().(*MetricPoint_SummaryValue); ok {\n\t\treturn x.SummaryValue\n\t}\n\treturn nil\n}\n\nfunc (m *MetricPoint) GetTimestamp() *types.Timestamp {\n\tif m != nil {\n\t\treturn m.Timestamp\n\t}\n\treturn nil\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*MetricPoint) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*MetricPoint_UnknownValue)(nil),\n\t\t(*MetricPoint_GaugeValue)(nil),\n\t\t(*MetricPoint_CounterValue)(nil),\n\t\t(*MetricPoint_HistogramValue)(nil),\n\t\t(*MetricPoint_StateSetValue)(nil),\n\t\t(*MetricPoint_InfoValue)(nil),\n\t\t(*MetricPoint_SummaryValue)(nil),\n\t}\n}\n\n// Value for UNKNOWN MetricPoint.\ntype UnknownValue struct {\n\t// Required.\n\t//\n\t// Types that are valid to be assigned to Value:\n\t//\t*UnknownValue_DoubleValue\n\t//\t*UnknownValue_IntValue\n\tValue                isUnknownValue_Value `protobuf_oneof:\"value\"`\n\tXXX_NoUnkeyedLiteral struct{}             `json:\"-\"`\n\tXXX_unrecognized     []byte               `json:\"-\"`\n\tXXX_sizecache        int32                `json:\"-\"`\n}\n\nfunc (m *UnknownValue) Reset()         { *m = UnknownValue{} }\nfunc (m *UnknownValue) String() string { return proto.CompactTextString(m) }\nfunc (*UnknownValue) ProtoMessage()    {}\nfunc (*UnknownValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{5}\n}\nfunc (m *UnknownValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *UnknownValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_UnknownValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *UnknownValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_UnknownValue.Merge(m, src)\n}\nfunc (m *UnknownValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *UnknownValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_UnknownValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_UnknownValue proto.InternalMessageInfo\n\ntype isUnknownValue_Value interface {\n\tisUnknownValue_Value()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype UnknownValue_DoubleValue struct {\n\tDoubleValue float64 `protobuf:\"fixed64,1,opt,name=double_value,json=doubleValue,proto3,oneof\" json:\"double_value,omitempty\"`\n}\ntype UnknownValue_IntValue struct {\n\tIntValue int64 `protobuf:\"varint,2,opt,name=int_value,json=intValue,proto3,oneof\" json:\"int_value,omitempty\"`\n}\n\nfunc (*UnknownValue_DoubleValue) isUnknownValue_Value() {}\nfunc (*UnknownValue_IntValue) isUnknownValue_Value()    {}\n\nfunc (m *UnknownValue) GetValue() isUnknownValue_Value {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\nfunc (m *UnknownValue) GetDoubleValue() float64 {\n\tif x, ok := m.GetValue().(*UnknownValue_DoubleValue); ok {\n\t\treturn x.DoubleValue\n\t}\n\treturn 0\n}\n\nfunc (m *UnknownValue) GetIntValue() int64 {\n\tif x, ok := m.GetValue().(*UnknownValue_IntValue); ok {\n\t\treturn x.IntValue\n\t}\n\treturn 0\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*UnknownValue) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*UnknownValue_DoubleValue)(nil),\n\t\t(*UnknownValue_IntValue)(nil),\n\t}\n}\n\n// Value for GAUGE MetricPoint.\ntype GaugeValue struct {\n\t// Required.\n\t//\n\t// Types that are valid to be assigned to Value:\n\t//\t*GaugeValue_DoubleValue\n\t//\t*GaugeValue_IntValue\n\tValue                isGaugeValue_Value `protobuf_oneof:\"value\"`\n\tXXX_NoUnkeyedLiteral struct{}           `json:\"-\"`\n\tXXX_unrecognized     []byte             `json:\"-\"`\n\tXXX_sizecache        int32              `json:\"-\"`\n}\n\nfunc (m *GaugeValue) Reset()         { *m = GaugeValue{} }\nfunc (m *GaugeValue) String() string { return proto.CompactTextString(m) }\nfunc (*GaugeValue) ProtoMessage()    {}\nfunc (*GaugeValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{6}\n}\nfunc (m *GaugeValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GaugeValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GaugeValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GaugeValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GaugeValue.Merge(m, src)\n}\nfunc (m *GaugeValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GaugeValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_GaugeValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GaugeValue proto.InternalMessageInfo\n\ntype isGaugeValue_Value interface {\n\tisGaugeValue_Value()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype GaugeValue_DoubleValue struct {\n\tDoubleValue float64 `protobuf:\"fixed64,1,opt,name=double_value,json=doubleValue,proto3,oneof\" json:\"double_value,omitempty\"`\n}\ntype GaugeValue_IntValue struct {\n\tIntValue int64 `protobuf:\"varint,2,opt,name=int_value,json=intValue,proto3,oneof\" json:\"int_value,omitempty\"`\n}\n\nfunc (*GaugeValue_DoubleValue) isGaugeValue_Value() {}\nfunc (*GaugeValue_IntValue) isGaugeValue_Value()    {}\n\nfunc (m *GaugeValue) GetValue() isGaugeValue_Value {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\nfunc (m *GaugeValue) GetDoubleValue() float64 {\n\tif x, ok := m.GetValue().(*GaugeValue_DoubleValue); ok {\n\t\treturn x.DoubleValue\n\t}\n\treturn 0\n}\n\nfunc (m *GaugeValue) GetIntValue() int64 {\n\tif x, ok := m.GetValue().(*GaugeValue_IntValue); ok {\n\t\treturn x.IntValue\n\t}\n\treturn 0\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*GaugeValue) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*GaugeValue_DoubleValue)(nil),\n\t\t(*GaugeValue_IntValue)(nil),\n\t}\n}\n\n// Value for COUNTER MetricPoint.\ntype CounterValue struct {\n\t// Required.\n\t//\n\t// Types that are valid to be assigned to Total:\n\t//\t*CounterValue_DoubleValue\n\t//\t*CounterValue_IntValue\n\tTotal isCounterValue_Total `protobuf_oneof:\"total\"`\n\t// The time values began being collected for this counter.\n\t// Optional.\n\tCreated *types.Timestamp `protobuf:\"bytes,3,opt,name=created,proto3\" json:\"created,omitempty\"`\n\t// Optional.\n\tExemplar             *Exemplar `protobuf:\"bytes,4,opt,name=exemplar,proto3\" json:\"exemplar,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *CounterValue) Reset()         { *m = CounterValue{} }\nfunc (m *CounterValue) String() string { return proto.CompactTextString(m) }\nfunc (*CounterValue) ProtoMessage()    {}\nfunc (*CounterValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{7}\n}\nfunc (m *CounterValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *CounterValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_CounterValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *CounterValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_CounterValue.Merge(m, src)\n}\nfunc (m *CounterValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *CounterValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_CounterValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_CounterValue proto.InternalMessageInfo\n\ntype isCounterValue_Total interface {\n\tisCounterValue_Total()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype CounterValue_DoubleValue struct {\n\tDoubleValue float64 `protobuf:\"fixed64,1,opt,name=double_value,json=doubleValue,proto3,oneof\" json:\"double_value,omitempty\"`\n}\ntype CounterValue_IntValue struct {\n\tIntValue uint64 `protobuf:\"varint,2,opt,name=int_value,json=intValue,proto3,oneof\" json:\"int_value,omitempty\"`\n}\n\nfunc (*CounterValue_DoubleValue) isCounterValue_Total() {}\nfunc (*CounterValue_IntValue) isCounterValue_Total()    {}\n\nfunc (m *CounterValue) GetTotal() isCounterValue_Total {\n\tif m != nil {\n\t\treturn m.Total\n\t}\n\treturn nil\n}\n\nfunc (m *CounterValue) GetDoubleValue() float64 {\n\tif x, ok := m.GetTotal().(*CounterValue_DoubleValue); ok {\n\t\treturn x.DoubleValue\n\t}\n\treturn 0\n}\n\nfunc (m *CounterValue) GetIntValue() uint64 {\n\tif x, ok := m.GetTotal().(*CounterValue_IntValue); ok {\n\t\treturn x.IntValue\n\t}\n\treturn 0\n}\n\nfunc (m *CounterValue) GetCreated() *types.Timestamp {\n\tif m != nil {\n\t\treturn m.Created\n\t}\n\treturn nil\n}\n\nfunc (m *CounterValue) GetExemplar() *Exemplar {\n\tif m != nil {\n\t\treturn m.Exemplar\n\t}\n\treturn nil\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*CounterValue) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*CounterValue_DoubleValue)(nil),\n\t\t(*CounterValue_IntValue)(nil),\n\t}\n}\n\n// Value for HISTOGRAM or GAUGE_HISTOGRAM MetricPoint.\ntype HistogramValue struct {\n\t// Optional.\n\t//\n\t// Types that are valid to be assigned to Sum:\n\t//\t*HistogramValue_DoubleValue\n\t//\t*HistogramValue_IntValue\n\tSum isHistogramValue_Sum `protobuf_oneof:\"sum\"`\n\t// Optional.\n\tCount uint64 `protobuf:\"varint,3,opt,name=count,proto3\" json:\"count,omitempty\"`\n\t// The time values began being collected for this histogram.\n\t// Optional.\n\tCreated *types.Timestamp `protobuf:\"bytes,4,opt,name=created,proto3\" json:\"created,omitempty\"`\n\t// Optional.\n\tBuckets              []*HistogramValue_Bucket `protobuf:\"bytes,5,rep,name=buckets,proto3\" json:\"buckets,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}                 `json:\"-\"`\n\tXXX_unrecognized     []byte                   `json:\"-\"`\n\tXXX_sizecache        int32                    `json:\"-\"`\n}\n\nfunc (m *HistogramValue) Reset()         { *m = HistogramValue{} }\nfunc (m *HistogramValue) String() string { return proto.CompactTextString(m) }\nfunc (*HistogramValue) ProtoMessage()    {}\nfunc (*HistogramValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{8}\n}\nfunc (m *HistogramValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *HistogramValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_HistogramValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *HistogramValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_HistogramValue.Merge(m, src)\n}\nfunc (m *HistogramValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *HistogramValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_HistogramValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_HistogramValue proto.InternalMessageInfo\n\ntype isHistogramValue_Sum interface {\n\tisHistogramValue_Sum()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype HistogramValue_DoubleValue struct {\n\tDoubleValue float64 `protobuf:\"fixed64,1,opt,name=double_value,json=doubleValue,proto3,oneof\" json:\"double_value,omitempty\"`\n}\ntype HistogramValue_IntValue struct {\n\tIntValue int64 `protobuf:\"varint,2,opt,name=int_value,json=intValue,proto3,oneof\" json:\"int_value,omitempty\"`\n}\n\nfunc (*HistogramValue_DoubleValue) isHistogramValue_Sum() {}\nfunc (*HistogramValue_IntValue) isHistogramValue_Sum()    {}\n\nfunc (m *HistogramValue) GetSum() isHistogramValue_Sum {\n\tif m != nil {\n\t\treturn m.Sum\n\t}\n\treturn nil\n}\n\nfunc (m *HistogramValue) GetDoubleValue() float64 {\n\tif x, ok := m.GetSum().(*HistogramValue_DoubleValue); ok {\n\t\treturn x.DoubleValue\n\t}\n\treturn 0\n}\n\nfunc (m *HistogramValue) GetIntValue() int64 {\n\tif x, ok := m.GetSum().(*HistogramValue_IntValue); ok {\n\t\treturn x.IntValue\n\t}\n\treturn 0\n}\n\nfunc (m *HistogramValue) GetCount() uint64 {\n\tif m != nil {\n\t\treturn m.Count\n\t}\n\treturn 0\n}\n\nfunc (m *HistogramValue) GetCreated() *types.Timestamp {\n\tif m != nil {\n\t\treturn m.Created\n\t}\n\treturn nil\n}\n\nfunc (m *HistogramValue) GetBuckets() []*HistogramValue_Bucket {\n\tif m != nil {\n\t\treturn m.Buckets\n\t}\n\treturn nil\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*HistogramValue) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*HistogramValue_DoubleValue)(nil),\n\t\t(*HistogramValue_IntValue)(nil),\n\t}\n}\n\n// Bucket is the number of values for a bucket in the histogram\n// with an optional exemplar.\ntype HistogramValue_Bucket struct {\n\t// Required.\n\tCount uint64 `protobuf:\"varint,1,opt,name=count,proto3\" json:\"count,omitempty\"`\n\t// Optional.\n\tUpperBound float64 `protobuf:\"fixed64,2,opt,name=upper_bound,json=upperBound,proto3\" json:\"upper_bound,omitempty\"`\n\t// Optional.\n\tExemplar             *Exemplar `protobuf:\"bytes,3,opt,name=exemplar,proto3\" json:\"exemplar,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *HistogramValue_Bucket) Reset()         { *m = HistogramValue_Bucket{} }\nfunc (m *HistogramValue_Bucket) String() string { return proto.CompactTextString(m) }\nfunc (*HistogramValue_Bucket) ProtoMessage()    {}\nfunc (*HistogramValue_Bucket) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{8, 0}\n}\nfunc (m *HistogramValue_Bucket) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *HistogramValue_Bucket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_HistogramValue_Bucket.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *HistogramValue_Bucket) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_HistogramValue_Bucket.Merge(m, src)\n}\nfunc (m *HistogramValue_Bucket) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *HistogramValue_Bucket) XXX_DiscardUnknown() {\n\txxx_messageInfo_HistogramValue_Bucket.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_HistogramValue_Bucket proto.InternalMessageInfo\n\nfunc (m *HistogramValue_Bucket) GetCount() uint64 {\n\tif m != nil {\n\t\treturn m.Count\n\t}\n\treturn 0\n}\n\nfunc (m *HistogramValue_Bucket) GetUpperBound() float64 {\n\tif m != nil {\n\t\treturn m.UpperBound\n\t}\n\treturn 0\n}\n\nfunc (m *HistogramValue_Bucket) GetExemplar() *Exemplar {\n\tif m != nil {\n\t\treturn m.Exemplar\n\t}\n\treturn nil\n}\n\ntype Exemplar struct {\n\t// Required.\n\tValue float64 `protobuf:\"fixed64,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\t// Optional.\n\tTimestamp *types.Timestamp `protobuf:\"bytes,2,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\t// Labels are additional information about the exemplar value (e.g. trace id).\n\t// Optional.\n\tLabel                []*Label `protobuf:\"bytes,3,rep,name=label,proto3\" json:\"label,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Exemplar) Reset()         { *m = Exemplar{} }\nfunc (m *Exemplar) String() string { return proto.CompactTextString(m) }\nfunc (*Exemplar) ProtoMessage()    {}\nfunc (*Exemplar) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{9}\n}\nfunc (m *Exemplar) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Exemplar) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Exemplar.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Exemplar) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Exemplar.Merge(m, src)\n}\nfunc (m *Exemplar) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Exemplar) XXX_DiscardUnknown() {\n\txxx_messageInfo_Exemplar.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Exemplar proto.InternalMessageInfo\n\nfunc (m *Exemplar) GetValue() float64 {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn 0\n}\n\nfunc (m *Exemplar) GetTimestamp() *types.Timestamp {\n\tif m != nil {\n\t\treturn m.Timestamp\n\t}\n\treturn nil\n}\n\nfunc (m *Exemplar) GetLabel() []*Label {\n\tif m != nil {\n\t\treturn m.Label\n\t}\n\treturn nil\n}\n\n// Value for STATE_SET MetricPoint.\ntype StateSetValue struct {\n\t// Optional.\n\tStates               []*StateSetValue_State `protobuf:\"bytes,1,rep,name=states,proto3\" json:\"states,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}               `json:\"-\"`\n\tXXX_unrecognized     []byte                 `json:\"-\"`\n\tXXX_sizecache        int32                  `json:\"-\"`\n}\n\nfunc (m *StateSetValue) Reset()         { *m = StateSetValue{} }\nfunc (m *StateSetValue) String() string { return proto.CompactTextString(m) }\nfunc (*StateSetValue) ProtoMessage()    {}\nfunc (*StateSetValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{10}\n}\nfunc (m *StateSetValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *StateSetValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_StateSetValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *StateSetValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_StateSetValue.Merge(m, src)\n}\nfunc (m *StateSetValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *StateSetValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_StateSetValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_StateSetValue proto.InternalMessageInfo\n\nfunc (m *StateSetValue) GetStates() []*StateSetValue_State {\n\tif m != nil {\n\t\treturn m.States\n\t}\n\treturn nil\n}\n\ntype StateSetValue_State struct {\n\t// Required.\n\tEnabled bool `protobuf:\"varint,1,opt,name=enabled,proto3\" json:\"enabled,omitempty\"`\n\t// Required.\n\tName                 string   `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *StateSetValue_State) Reset()         { *m = StateSetValue_State{} }\nfunc (m *StateSetValue_State) String() string { return proto.CompactTextString(m) }\nfunc (*StateSetValue_State) ProtoMessage()    {}\nfunc (*StateSetValue_State) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{10, 0}\n}\nfunc (m *StateSetValue_State) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *StateSetValue_State) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_StateSetValue_State.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *StateSetValue_State) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_StateSetValue_State.Merge(m, src)\n}\nfunc (m *StateSetValue_State) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *StateSetValue_State) XXX_DiscardUnknown() {\n\txxx_messageInfo_StateSetValue_State.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_StateSetValue_State proto.InternalMessageInfo\n\nfunc (m *StateSetValue_State) GetEnabled() bool {\n\tif m != nil {\n\t\treturn m.Enabled\n\t}\n\treturn false\n}\n\nfunc (m *StateSetValue_State) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\n// Value for INFO MetricPoint.\ntype InfoValue struct {\n\t// Optional.\n\tInfo                 []*Label `protobuf:\"bytes,1,rep,name=info,proto3\" json:\"info,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *InfoValue) Reset()         { *m = InfoValue{} }\nfunc (m *InfoValue) String() string { return proto.CompactTextString(m) }\nfunc (*InfoValue) ProtoMessage()    {}\nfunc (*InfoValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{11}\n}\nfunc (m *InfoValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *InfoValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_InfoValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *InfoValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_InfoValue.Merge(m, src)\n}\nfunc (m *InfoValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *InfoValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_InfoValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_InfoValue proto.InternalMessageInfo\n\nfunc (m *InfoValue) GetInfo() []*Label {\n\tif m != nil {\n\t\treturn m.Info\n\t}\n\treturn nil\n}\n\n// Value for SUMMARY MetricPoint.\ntype SummaryValue struct {\n\t// Optional.\n\t//\n\t// Types that are valid to be assigned to Sum:\n\t//\t*SummaryValue_DoubleValue\n\t//\t*SummaryValue_IntValue\n\tSum isSummaryValue_Sum `protobuf_oneof:\"sum\"`\n\t// Optional.\n\tCount uint64 `protobuf:\"varint,3,opt,name=count,proto3\" json:\"count,omitempty\"`\n\t// The time sum and count values began being collected for this summary.\n\t// Optional.\n\tCreated *types.Timestamp `protobuf:\"bytes,4,opt,name=created,proto3\" json:\"created,omitempty\"`\n\t// Optional.\n\tQuantile             []*SummaryValue_Quantile `protobuf:\"bytes,5,rep,name=quantile,proto3\" json:\"quantile,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}                 `json:\"-\"`\n\tXXX_unrecognized     []byte                   `json:\"-\"`\n\tXXX_sizecache        int32                    `json:\"-\"`\n}\n\nfunc (m *SummaryValue) Reset()         { *m = SummaryValue{} }\nfunc (m *SummaryValue) String() string { return proto.CompactTextString(m) }\nfunc (*SummaryValue) ProtoMessage()    {}\nfunc (*SummaryValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{12}\n}\nfunc (m *SummaryValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *SummaryValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_SummaryValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *SummaryValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_SummaryValue.Merge(m, src)\n}\nfunc (m *SummaryValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *SummaryValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_SummaryValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_SummaryValue proto.InternalMessageInfo\n\ntype isSummaryValue_Sum interface {\n\tisSummaryValue_Sum()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype SummaryValue_DoubleValue struct {\n\tDoubleValue float64 `protobuf:\"fixed64,1,opt,name=double_value,json=doubleValue,proto3,oneof\" json:\"double_value,omitempty\"`\n}\ntype SummaryValue_IntValue struct {\n\tIntValue int64 `protobuf:\"varint,2,opt,name=int_value,json=intValue,proto3,oneof\" json:\"int_value,omitempty\"`\n}\n\nfunc (*SummaryValue_DoubleValue) isSummaryValue_Sum() {}\nfunc (*SummaryValue_IntValue) isSummaryValue_Sum()    {}\n\nfunc (m *SummaryValue) GetSum() isSummaryValue_Sum {\n\tif m != nil {\n\t\treturn m.Sum\n\t}\n\treturn nil\n}\n\nfunc (m *SummaryValue) GetDoubleValue() float64 {\n\tif x, ok := m.GetSum().(*SummaryValue_DoubleValue); ok {\n\t\treturn x.DoubleValue\n\t}\n\treturn 0\n}\n\nfunc (m *SummaryValue) GetIntValue() int64 {\n\tif x, ok := m.GetSum().(*SummaryValue_IntValue); ok {\n\t\treturn x.IntValue\n\t}\n\treturn 0\n}\n\nfunc (m *SummaryValue) GetCount() uint64 {\n\tif m != nil {\n\t\treturn m.Count\n\t}\n\treturn 0\n}\n\nfunc (m *SummaryValue) GetCreated() *types.Timestamp {\n\tif m != nil {\n\t\treturn m.Created\n\t}\n\treturn nil\n}\n\nfunc (m *SummaryValue) GetQuantile() []*SummaryValue_Quantile {\n\tif m != nil {\n\t\treturn m.Quantile\n\t}\n\treturn nil\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*SummaryValue) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*SummaryValue_DoubleValue)(nil),\n\t\t(*SummaryValue_IntValue)(nil),\n\t}\n}\n\ntype SummaryValue_Quantile struct {\n\t// Required.\n\tQuantile float64 `protobuf:\"fixed64,1,opt,name=quantile,proto3\" json:\"quantile,omitempty\"`\n\t// Required.\n\tValue                float64  `protobuf:\"fixed64,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *SummaryValue_Quantile) Reset()         { *m = SummaryValue_Quantile{} }\nfunc (m *SummaryValue_Quantile) String() string { return proto.CompactTextString(m) }\nfunc (*SummaryValue_Quantile) ProtoMessage()    {}\nfunc (*SummaryValue_Quantile) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0b803df83757ec01, []int{12, 0}\n}\nfunc (m *SummaryValue_Quantile) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *SummaryValue_Quantile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_SummaryValue_Quantile.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *SummaryValue_Quantile) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_SummaryValue_Quantile.Merge(m, src)\n}\nfunc (m *SummaryValue_Quantile) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *SummaryValue_Quantile) XXX_DiscardUnknown() {\n\txxx_messageInfo_SummaryValue_Quantile.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_SummaryValue_Quantile proto.InternalMessageInfo\n\nfunc (m *SummaryValue_Quantile) GetQuantile() float64 {\n\tif m != nil {\n\t\treturn m.Quantile\n\t}\n\treturn 0\n}\n\nfunc (m *SummaryValue_Quantile) GetValue() float64 {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn 0\n}\n\nfunc init() {\n\tproto.RegisterEnum(\"jaeger.api_v2.metrics.MetricType\", MetricType_name, MetricType_value)\n\tproto.RegisterType((*MetricSet)(nil), \"jaeger.api_v2.metrics.MetricSet\")\n\tproto.RegisterType((*MetricFamily)(nil), \"jaeger.api_v2.metrics.MetricFamily\")\n\tproto.RegisterType((*Metric)(nil), \"jaeger.api_v2.metrics.Metric\")\n\tproto.RegisterType((*Label)(nil), \"jaeger.api_v2.metrics.Label\")\n\tproto.RegisterType((*MetricPoint)(nil), \"jaeger.api_v2.metrics.MetricPoint\")\n\tproto.RegisterType((*UnknownValue)(nil), \"jaeger.api_v2.metrics.UnknownValue\")\n\tproto.RegisterType((*GaugeValue)(nil), \"jaeger.api_v2.metrics.GaugeValue\")\n\tproto.RegisterType((*CounterValue)(nil), \"jaeger.api_v2.metrics.CounterValue\")\n\tproto.RegisterType((*HistogramValue)(nil), \"jaeger.api_v2.metrics.HistogramValue\")\n\tproto.RegisterType((*HistogramValue_Bucket)(nil), \"jaeger.api_v2.metrics.HistogramValue.Bucket\")\n\tproto.RegisterType((*Exemplar)(nil), \"jaeger.api_v2.metrics.Exemplar\")\n\tproto.RegisterType((*StateSetValue)(nil), \"jaeger.api_v2.metrics.StateSetValue\")\n\tproto.RegisterType((*StateSetValue_State)(nil), \"jaeger.api_v2.metrics.StateSetValue.State\")\n\tproto.RegisterType((*InfoValue)(nil), \"jaeger.api_v2.metrics.InfoValue\")\n\tproto.RegisterType((*SummaryValue)(nil), \"jaeger.api_v2.metrics.SummaryValue\")\n\tproto.RegisterType((*SummaryValue_Quantile)(nil), \"jaeger.api_v2.metrics.SummaryValue.Quantile\")\n}\n\nfunc init() { proto.RegisterFile(\"openmetrics.proto\", fileDescriptor_0b803df83757ec01) }\n\nvar fileDescriptor_0b803df83757ec01 = []byte{\n\t// 981 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x96, 0xdd, 0x6e, 0xe3, 0x44,\n\t0x14, 0xc7, 0xeb, 0xc4, 0xf9, 0x3a, 0x49, 0xda, 0x30, 0x2c, 0x92, 0x15, 0xb1, 0x6d, 0xf1, 0x82,\n\t0x54, 0xad, 0x90, 0x0b, 0x65, 0x17, 0x90, 0x80, 0x8b, 0x64, 0x49, 0x93, 0xc2, 0x36, 0x5d, 0x26,\n\t0x09, 0xa8, 0x70, 0x11, 0x39, 0xe9, 0xd4, 0x35, 0xeb, 0x2f, 0xec, 0xf1, 0x42, 0x05, 0xf7, 0x48,\n\t0x5c, 0xf0, 0x26, 0xbc, 0x00, 0x4f, 0xc0, 0x15, 0xe2, 0x0d, 0x40, 0xbd, 0xe7, 0x1d, 0xd0, 0x7c,\n\t0xc5, 0x0e, 0xda, 0x84, 0x2c, 0xda, 0x8b, 0xbd, 0x9b, 0x73, 0xfc, 0x3f, 0xbf, 0x99, 0x73, 0x7c,\n\t0x7c, 0xc6, 0xf0, 0x52, 0x18, 0x91, 0xc0, 0x27, 0x34, 0x76, 0xe7, 0x89, 0x15, 0xc5, 0x21, 0x0d,\n\t0xd1, 0x2b, 0x5f, 0xdb, 0xc4, 0x21, 0xb1, 0x65, 0x47, 0xee, 0xf4, 0xc9, 0x91, 0x25, 0x1f, 0xb6,\n\t0xf7, 0x9c, 0x30, 0x74, 0x3c, 0x72, 0xc8, 0x45, 0xb3, 0xf4, 0xf2, 0x90, 0xba, 0x3e, 0x49, 0xa8,\n\t0xed, 0x47, 0x22, 0xae, 0x7d, 0xcb, 0x09, 0x9d, 0x90, 0x2f, 0x0f, 0xd9, 0x4a, 0x78, 0xcd, 0x73,\n\t0xa8, 0x9d, 0x72, 0xc2, 0x88, 0x50, 0xf4, 0x10, 0x76, 0x04, 0x6e, 0x7a, 0x69, 0xfb, 0xae, 0xe7,\n\t0x92, 0xc4, 0xd0, 0xf6, 0x8b, 0x07, 0xf5, 0xa3, 0x3b, 0xd6, 0x53, 0x37, 0xb5, 0x44, 0xe8, 0x31,\n\t0x13, 0x5f, 0xe3, 0x6d, 0x3f, 0xb3, 0x5c, 0x92, 0x98, 0xbf, 0x6a, 0xd0, 0xc8, 0x0b, 0x10, 0x02,\n\t0x3d, 0xb0, 0x7d, 0x62, 0x68, 0xfb, 0xda, 0x41, 0x0d, 0xf3, 0x35, 0xba, 0x0f, 0x3a, 0xbd, 0x8e,\n\t0x88, 0x51, 0xd8, 0xd7, 0x0e, 0xb6, 0x8f, 0x5e, 0x5b, 0xbb, 0xcf, 0xf8, 0x3a, 0x22, 0x98, 0xcb,\n\t0x19, 0x2a, 0x0d, 0x5c, 0x6a, 0x14, 0x05, 0x8a, 0xad, 0x99, 0xef, 0x8a, 0x78, 0x91, 0xa1, 0x0b,\n\t0x1f, 0x5b, 0xa3, 0xf7, 0xa0, 0x22, 0x19, 0x46, 0x89, 0x67, 0x72, 0x7b, 0xed, 0x0e, 0x58, 0xa9,\n\t0xcd, 0x1f, 0x35, 0x28, 0x0b, 0x1f, 0xba, 0x07, 0x65, 0xcf, 0x9e, 0x11, 0x4f, 0x15, 0xe3, 0xd5,\n\t0x15, 0x88, 0x87, 0x4c, 0x84, 0xa5, 0x16, 0xf5, 0xa1, 0x29, 0x6b, 0x19, 0x85, 0x6e, 0x40, 0x13,\n\t0xa3, 0xc0, 0x83, 0xcd, 0xb5, 0xfb, 0x3f, 0x62, 0x52, 0xdc, 0xf0, 0x33, 0x23, 0x31, 0xdf, 0x86,\n\t0x12, 0x27, 0x3f, 0xb5, 0x7c, 0xb7, 0xa0, 0xf4, 0xc4, 0xf6, 0x52, 0x51, 0xbf, 0x1a, 0x16, 0x86,\n\t0xf9, 0xa7, 0x0e, 0xf5, 0x1c, 0x10, 0x7d, 0x02, 0xcd, 0x34, 0x78, 0x1c, 0x84, 0xdf, 0x06, 0x53,\n\t0xa1, 0x66, 0x88, 0xd5, 0x6f, 0x75, 0x22, 0xb4, 0x9f, 0x33, 0xe9, 0x60, 0x0b, 0x37, 0xd2, 0x9c,\n\t0x8d, 0x3e, 0x86, 0xba, 0x63, 0xa7, 0x0e, 0x99, 0x66, 0xfb, 0xd6, 0x57, 0xbe, 0xb7, 0x3e, 0x53,\n\t0x2a, 0x0e, 0x38, 0x0b, 0x8b, 0x9d, 0x68, 0x1e, 0xa6, 0x01, 0x25, 0xb1, 0xe4, 0x14, 0xd7, 0x9e,\n\t0xe8, 0x81, 0xd0, 0x2e, 0x4e, 0x34, 0xcf, 0xd9, 0xe8, 0x11, 0xec, 0x5c, 0xb9, 0x09, 0x0d, 0x9d,\n\t0xd8, 0xf6, 0x25, 0x4d, 0xe7, 0xb4, 0x37, 0x56, 0xd0, 0x06, 0x4a, 0xad, 0x78, 0xdb, 0x57, 0x4b,\n\t0x1e, 0x34, 0x84, 0x9d, 0x84, 0xda, 0x94, 0x4c, 0x13, 0x42, 0x25, 0xb1, 0xc4, 0x89, 0xaf, 0xaf,\n\t0x20, 0x8e, 0x98, 0x7a, 0x44, 0xa8, 0x02, 0x36, 0x93, 0xbc, 0x03, 0x75, 0x00, 0xdc, 0xe0, 0x32,\n\t0x94, 0xa8, 0x32, 0x47, 0xed, 0xaf, 0x40, 0x9d, 0x04, 0x97, 0xa1, 0xc2, 0xd4, 0x5c, 0x65, 0xb0,\n\t0x82, 0x25, 0xa9, 0xef, 0xdb, 0xf1, 0xb5, 0xa4, 0x54, 0xd6, 0x16, 0x6c, 0x24, 0xb4, 0x8b, 0x82,\n\t0x25, 0x39, 0x1b, 0xbd, 0x0f, 0xb5, 0xc5, 0x70, 0x30, 0xaa, 0x9c, 0xd3, 0xb6, 0xc4, 0xf8, 0xb0,\n\t0xd4, 0xf8, 0xb0, 0xc6, 0x4a, 0x81, 0x33, 0x71, 0xb7, 0x22, 0xdb, 0xcd, 0xfc, 0x0a, 0x1a, 0xf9,\n\t0x2e, 0x41, 0x77, 0xa0, 0x71, 0x11, 0xa6, 0x33, 0x8f, 0xe4, 0x1a, 0x4c, 0x1b, 0x6c, 0xe1, 0xba,\n\t0xf0, 0x0a, 0xd1, 0x6d, 0xa8, 0xb9, 0x01, 0xcd, 0x35, 0x4e, 0x71, 0xb0, 0x85, 0xab, 0x6e, 0x20,\n\t0xaa, 0x94, 0xc1, 0xcf, 0x01, 0xb2, 0xc6, 0x79, 0xbe, 0xe8, 0xdf, 0x35, 0x68, 0xe4, 0x9b, 0xe9,\n\t0x7f, 0xd2, 0xf5, 0x3c, 0x1d, 0xdd, 0x83, 0xca, 0x3c, 0x26, 0x36, 0x25, 0x17, 0xb2, 0x8d, 0xd7,\n\t0x55, 0x53, 0x49, 0xd1, 0x07, 0x50, 0x25, 0xdf, 0x11, 0x3f, 0xf2, 0xec, 0x58, 0xf6, 0xeb, 0xde,\n\t0x8a, 0x97, 0xd9, 0x93, 0x32, 0xbc, 0x08, 0x60, 0x09, 0xd1, 0x90, 0xda, 0x9e, 0xf9, 0x77, 0x01,\n\t0xb6, 0x97, 0xfb, 0xf9, 0x79, 0x14, 0x8c, 0xcd, 0x15, 0xfe, 0x8d, 0xf1, 0x84, 0x74, 0x2c, 0x8c,\n\t0x7c, 0xa2, 0xfa, 0xe6, 0x89, 0x1e, 0x43, 0x65, 0x96, 0xce, 0x1f, 0x13, 0xaa, 0x66, 0xf0, 0x9b,\n\t0x1b, 0x7d, 0x97, 0x56, 0x97, 0x07, 0x61, 0x15, 0xdc, 0xfe, 0x01, 0xca, 0xc2, 0x95, 0x9d, 0x4e,\n\t0xcb, 0x9f, 0x6e, 0x0f, 0xea, 0x69, 0x14, 0x91, 0x78, 0x3a, 0x0b, 0xd3, 0xe0, 0x82, 0x27, 0xa5,\n\t0x61, 0xe0, 0xae, 0x2e, 0xf3, 0x2c, 0x55, 0xbc, 0xf8, 0xac, 0x15, 0x2f, 0x41, 0x31, 0x49, 0x7d,\n\t0xf3, 0x67, 0x0d, 0xaa, 0xea, 0x69, 0x36, 0x7d, 0x79, 0x89, 0xe5, 0xf4, 0x5d, 0xfe, 0xbc, 0x0a,\n\t0xcf, 0xf0, 0x79, 0xa1, 0x23, 0x28, 0xf1, 0xdb, 0xc3, 0x28, 0x6e, 0x70, 0xd1, 0x08, 0xa9, 0xf9,\n\t0x93, 0x06, 0xcd, 0xa5, 0xf1, 0x83, 0xba, 0x50, 0xe6, 0xe3, 0x47, 0xdd, 0x57, 0x77, 0x37, 0x19,\n\t0x5a, 0xc2, 0xc2, 0x32, 0xb2, 0x7d, 0x1f, 0x4a, 0xdc, 0x81, 0x0c, 0xa8, 0x90, 0xc0, 0x9e, 0x79,\n\t0xe4, 0x82, 0x27, 0x59, 0xc5, 0xca, 0x5c, 0x5c, 0x47, 0x85, 0xec, 0x3a, 0x32, 0x3f, 0x82, 0xda,\n\t0x62, 0x7e, 0xa1, 0xb7, 0x40, 0x67, 0xf3, 0x6b, 0xa3, 0x5b, 0x93, 0x2b, 0xcd, 0x5f, 0x0a, 0xd0,\n\t0xc8, 0x4f, 0xae, 0x17, 0xae, 0x95, 0x07, 0x50, 0xfd, 0x26, 0xb5, 0x03, 0xea, 0x7a, 0xe4, 0x3f,\n\t0x7a, 0x39, 0x9f, 0x86, 0xf5, 0x99, 0x8c, 0xc1, 0x8b, 0xe8, 0xf6, 0x87, 0x50, 0x55, 0x5e, 0xd4,\n\t0xce, 0x51, 0x45, 0x27, 0x2d, 0xec, 0xe5, 0x0b, 0x5e, 0xb5, 0x98, 0x6c, 0xc6, 0xbb, 0xdf, 0x03,\n\t0x64, 0x7f, 0x46, 0xa8, 0x0e, 0x95, 0xc9, 0xf0, 0xd3, 0xe1, 0xd9, 0x17, 0xc3, 0xd6, 0x16, 0xaa,\n\t0x41, 0xa9, 0xdf, 0x99, 0xf4, 0x7b, 0x2d, 0x8d, 0xf9, 0x1f, 0x9c, 0x4d, 0x86, 0xe3, 0x1e, 0x6e,\n\t0x15, 0x50, 0x13, 0x6a, 0xa3, 0x71, 0x67, 0xdc, 0x9b, 0x8e, 0x7a, 0xe3, 0x56, 0x11, 0x55, 0x41,\n\t0x3f, 0x19, 0x1e, 0x9f, 0xb5, 0x74, 0xf6, 0x60, 0x70, 0x32, 0x1a, 0x9f, 0xf5, 0x71, 0xe7, 0xb4,\n\t0x55, 0x42, 0x2f, 0xc3, 0x0e, 0x8f, 0x9f, 0x66, 0xce, 0x32, 0x23, 0x8d, 0x26, 0xa7, 0xa7, 0x1d,\n\t0x7c, 0xde, 0xaa, 0x74, 0xdf, 0xfd, 0xed, 0x66, 0x57, 0xfb, 0xe3, 0x66, 0x57, 0xfb, 0xeb, 0x66,\n\t0x57, 0x83, 0x3d, 0x37, 0x94, 0x95, 0xa0, 0xb1, 0x3d, 0x77, 0x03, 0xe7, 0x5f, 0x05, 0xf9, 0x52,\n\t0xfd, 0x59, 0xcd, 0xca, 0xbc, 0xc0, 0xef, 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x75, 0xe1,\n\t0xe3, 0xdb, 0x0a, 0x00, 0x00,\n}\n\nfunc (m *MetricSet) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *MetricSet) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricSet) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.MetricFamilies) > 0 {\n\t\tfor iNdEx := len(m.MetricFamilies) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.MetricFamilies[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *MetricFamily) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *MetricFamily) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricFamily) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Metrics) > 0 {\n\t\tfor iNdEx := len(m.Metrics) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Metrics[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x2a\n\t\t}\n\t}\n\tif len(m.Help) > 0 {\n\t\ti -= len(m.Help)\n\t\tcopy(dAtA[i:], m.Help)\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(len(m.Help)))\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\tif len(m.Unit) > 0 {\n\t\ti -= len(m.Unit)\n\t\tcopy(dAtA[i:], m.Unit)\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(len(m.Unit)))\n\t\ti--\n\t\tdAtA[i] = 0x1a\n\t}\n\tif m.Type != 0 {\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.Type))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif len(m.Name) > 0 {\n\t\ti -= len(m.Name)\n\t\tcopy(dAtA[i:], m.Name)\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(len(m.Name)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Metric) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Metric) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Metric) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.MetricPoints) > 0 {\n\t\tfor iNdEx := len(m.MetricPoints) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.MetricPoints[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x12\n\t\t}\n\t}\n\tif len(m.Labels) > 0 {\n\t\tfor iNdEx := len(m.Labels) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Labels[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Label) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Label) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Label) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Value) > 0 {\n\t\ti -= len(m.Value)\n\t\tcopy(dAtA[i:], m.Value)\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(len(m.Value)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Name) > 0 {\n\t\ti -= len(m.Name)\n\t\tcopy(dAtA[i:], m.Name)\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(len(m.Name)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *MetricPoint) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *MetricPoint) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Timestamp != nil {\n\t\t{\n\t\t\tsize, err := m.Timestamp.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x42\n\t}\n\tif m.Value != nil {\n\t\t{\n\t\t\tsize := m.Value.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Value.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *MetricPoint_UnknownValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_UnknownValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.UnknownValue != nil {\n\t\t{\n\t\t\tsize, err := m.UnknownValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *MetricPoint_GaugeValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_GaugeValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.GaugeValue != nil {\n\t\t{\n\t\t\tsize, err := m.GaugeValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *MetricPoint_CounterValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_CounterValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.CounterValue != nil {\n\t\t{\n\t\t\tsize, err := m.CounterValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x1a\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *MetricPoint_HistogramValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_HistogramValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.HistogramValue != nil {\n\t\t{\n\t\t\tsize, err := m.HistogramValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *MetricPoint_StateSetValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_StateSetValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.StateSetValue != nil {\n\t\t{\n\t\t\tsize, err := m.StateSetValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x2a\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *MetricPoint_InfoValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_InfoValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.InfoValue != nil {\n\t\t{\n\t\t\tsize, err := m.InfoValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x32\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *MetricPoint_SummaryValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *MetricPoint_SummaryValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.SummaryValue != nil {\n\t\t{\n\t\t\tsize, err := m.SummaryValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x3a\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *UnknownValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *UnknownValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *UnknownValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Value != nil {\n\t\t{\n\t\t\tsize := m.Value.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Value.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *UnknownValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *UnknownValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= 8\n\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue))))\n\ti--\n\tdAtA[i] = 0x9\n\treturn len(dAtA) - i, nil\n}\nfunc (m *UnknownValue_IntValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *UnknownValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.IntValue))\n\ti--\n\tdAtA[i] = 0x10\n\treturn len(dAtA) - i, nil\n}\nfunc (m *GaugeValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GaugeValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GaugeValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Value != nil {\n\t\t{\n\t\t\tsize := m.Value.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Value.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GaugeValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GaugeValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= 8\n\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue))))\n\ti--\n\tdAtA[i] = 0x9\n\treturn len(dAtA) - i, nil\n}\nfunc (m *GaugeValue_IntValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GaugeValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.IntValue))\n\ti--\n\tdAtA[i] = 0x10\n\treturn len(dAtA) - i, nil\n}\nfunc (m *CounterValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *CounterValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CounterValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Exemplar != nil {\n\t\t{\n\t\t\tsize, err := m.Exemplar.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\tif m.Created != nil {\n\t\t{\n\t\t\tsize, err := m.Created.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x1a\n\t}\n\tif m.Total != nil {\n\t\t{\n\t\t\tsize := m.Total.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Total.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *CounterValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CounterValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= 8\n\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue))))\n\ti--\n\tdAtA[i] = 0x9\n\treturn len(dAtA) - i, nil\n}\nfunc (m *CounterValue_IntValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CounterValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.IntValue))\n\ti--\n\tdAtA[i] = 0x10\n\treturn len(dAtA) - i, nil\n}\nfunc (m *HistogramValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *HistogramValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *HistogramValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Buckets) > 0 {\n\t\tfor iNdEx := len(m.Buckets) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Buckets[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x2a\n\t\t}\n\t}\n\tif m.Created != nil {\n\t\t{\n\t\t\tsize, err := m.Created.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\tif m.Count != 0 {\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.Count))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif m.Sum != nil {\n\t\t{\n\t\t\tsize := m.Sum.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Sum.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *HistogramValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *HistogramValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= 8\n\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue))))\n\ti--\n\tdAtA[i] = 0x9\n\treturn len(dAtA) - i, nil\n}\nfunc (m *HistogramValue_IntValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *HistogramValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.IntValue))\n\ti--\n\tdAtA[i] = 0x10\n\treturn len(dAtA) - i, nil\n}\nfunc (m *HistogramValue_Bucket) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *HistogramValue_Bucket) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *HistogramValue_Bucket) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Exemplar != nil {\n\t\t{\n\t\t\tsize, err := m.Exemplar.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x1a\n\t}\n\tif m.UpperBound != 0 {\n\t\ti -= 8\n\t\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.UpperBound))))\n\t\ti--\n\t\tdAtA[i] = 0x11\n\t}\n\tif m.Count != 0 {\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.Count))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Exemplar) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Exemplar) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Exemplar) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Label) > 0 {\n\t\tfor iNdEx := len(m.Label) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Label[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x1a\n\t\t}\n\t}\n\tif m.Timestamp != nil {\n\t\t{\n\t\t\tsize, err := m.Timestamp.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif m.Value != 0 {\n\t\ti -= 8\n\t\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value))))\n\t\ti--\n\t\tdAtA[i] = 0x9\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *StateSetValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *StateSetValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *StateSetValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.States) > 0 {\n\t\tfor iNdEx := len(m.States) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.States[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *StateSetValue_State) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *StateSetValue_State) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *StateSetValue_State) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Name) > 0 {\n\t\ti -= len(m.Name)\n\t\tcopy(dAtA[i:], m.Name)\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(len(m.Name)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif m.Enabled {\n\t\ti--\n\t\tif m.Enabled {\n\t\t\tdAtA[i] = 1\n\t\t} else {\n\t\t\tdAtA[i] = 0\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *InfoValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *InfoValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *InfoValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Info) > 0 {\n\t\tfor iNdEx := len(m.Info) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Info[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *SummaryValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *SummaryValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *SummaryValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Quantile) > 0 {\n\t\tfor iNdEx := len(m.Quantile) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Quantile[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x2a\n\t\t}\n\t}\n\tif m.Created != nil {\n\t\t{\n\t\t\tsize, err := m.Created.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\tif m.Count != 0 {\n\t\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.Count))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif m.Sum != nil {\n\t\t{\n\t\t\tsize := m.Sum.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Sum.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *SummaryValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *SummaryValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= 8\n\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue))))\n\ti--\n\tdAtA[i] = 0x9\n\treturn len(dAtA) - i, nil\n}\nfunc (m *SummaryValue_IntValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *SummaryValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti = encodeVarintOpenmetrics(dAtA, i, uint64(m.IntValue))\n\ti--\n\tdAtA[i] = 0x10\n\treturn len(dAtA) - i, nil\n}\nfunc (m *SummaryValue_Quantile) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *SummaryValue_Quantile) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *SummaryValue_Quantile) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Value != 0 {\n\t\ti -= 8\n\t\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value))))\n\t\ti--\n\t\tdAtA[i] = 0x11\n\t}\n\tif m.Quantile != 0 {\n\t\ti -= 8\n\t\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Quantile))))\n\t\ti--\n\t\tdAtA[i] = 0x9\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc encodeVarintOpenmetrics(dAtA []byte, offset int, v uint64) int {\n\toffset -= sovOpenmetrics(v)\n\tbase := offset\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn base\n}\nfunc (m *MetricSet) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.MetricFamilies) > 0 {\n\t\tfor _, e := range m.MetricFamilies {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *MetricFamily) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Name)\n\tif l > 0 {\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.Type != 0 {\n\t\tn += 1 + sovOpenmetrics(uint64(m.Type))\n\t}\n\tl = len(m.Unit)\n\tif l > 0 {\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tl = len(m.Help)\n\tif l > 0 {\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif len(m.Metrics) > 0 {\n\t\tfor _, e := range m.Metrics {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Metric) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Labels) > 0 {\n\t\tfor _, e := range m.Labels {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif len(m.MetricPoints) > 0 {\n\t\tfor _, e := range m.MetricPoints {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Label) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Name)\n\tif l > 0 {\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tl = len(m.Value)\n\tif l > 0 {\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *MetricPoint) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Value != nil {\n\t\tn += m.Value.Size()\n\t}\n\tif m.Timestamp != nil {\n\t\tl = m.Timestamp.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *MetricPoint_UnknownValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.UnknownValue != nil {\n\t\tl = m.UnknownValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *MetricPoint_GaugeValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.GaugeValue != nil {\n\t\tl = m.GaugeValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *MetricPoint_CounterValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.CounterValue != nil {\n\t\tl = m.CounterValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *MetricPoint_HistogramValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.HistogramValue != nil {\n\t\tl = m.HistogramValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *MetricPoint_StateSetValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.StateSetValue != nil {\n\t\tl = m.StateSetValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *MetricPoint_InfoValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.InfoValue != nil {\n\t\tl = m.InfoValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *MetricPoint_SummaryValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.SummaryValue != nil {\n\t\tl = m.SummaryValue.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *UnknownValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Value != nil {\n\t\tn += m.Value.Size()\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *UnknownValue_DoubleValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 9\n\treturn n\n}\nfunc (m *UnknownValue_IntValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 1 + sovOpenmetrics(uint64(m.IntValue))\n\treturn n\n}\nfunc (m *GaugeValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Value != nil {\n\t\tn += m.Value.Size()\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GaugeValue_DoubleValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 9\n\treturn n\n}\nfunc (m *GaugeValue_IntValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 1 + sovOpenmetrics(uint64(m.IntValue))\n\treturn n\n}\nfunc (m *CounterValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Total != nil {\n\t\tn += m.Total.Size()\n\t}\n\tif m.Created != nil {\n\t\tl = m.Created.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.Exemplar != nil {\n\t\tl = m.Exemplar.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *CounterValue_DoubleValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 9\n\treturn n\n}\nfunc (m *CounterValue_IntValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 1 + sovOpenmetrics(uint64(m.IntValue))\n\treturn n\n}\nfunc (m *HistogramValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Sum != nil {\n\t\tn += m.Sum.Size()\n\t}\n\tif m.Count != 0 {\n\t\tn += 1 + sovOpenmetrics(uint64(m.Count))\n\t}\n\tif m.Created != nil {\n\t\tl = m.Created.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif len(m.Buckets) > 0 {\n\t\tfor _, e := range m.Buckets {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *HistogramValue_DoubleValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 9\n\treturn n\n}\nfunc (m *HistogramValue_IntValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 1 + sovOpenmetrics(uint64(m.IntValue))\n\treturn n\n}\nfunc (m *HistogramValue_Bucket) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Count != 0 {\n\t\tn += 1 + sovOpenmetrics(uint64(m.Count))\n\t}\n\tif m.UpperBound != 0 {\n\t\tn += 9\n\t}\n\tif m.Exemplar != nil {\n\t\tl = m.Exemplar.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Exemplar) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Value != 0 {\n\t\tn += 9\n\t}\n\tif m.Timestamp != nil {\n\t\tl = m.Timestamp.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif len(m.Label) > 0 {\n\t\tfor _, e := range m.Label {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *StateSetValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.States) > 0 {\n\t\tfor _, e := range m.States {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *StateSetValue_State) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Enabled {\n\t\tn += 2\n\t}\n\tl = len(m.Name)\n\tif l > 0 {\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *InfoValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Info) > 0 {\n\t\tfor _, e := range m.Info {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *SummaryValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Sum != nil {\n\t\tn += m.Sum.Size()\n\t}\n\tif m.Count != 0 {\n\t\tn += 1 + sovOpenmetrics(uint64(m.Count))\n\t}\n\tif m.Created != nil {\n\t\tl = m.Created.Size()\n\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t}\n\tif len(m.Quantile) > 0 {\n\t\tfor _, e := range m.Quantile {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovOpenmetrics(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *SummaryValue_DoubleValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 9\n\treturn n\n}\nfunc (m *SummaryValue_IntValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 1 + sovOpenmetrics(uint64(m.IntValue))\n\treturn n\n}\nfunc (m *SummaryValue_Quantile) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Quantile != 0 {\n\t\tn += 9\n\t}\n\tif m.Value != 0 {\n\t\tn += 9\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc sovOpenmetrics(x uint64) (n int) {\n\treturn (math_bits.Len64(x|1) + 6) / 7\n}\nfunc sozOpenmetrics(x uint64) (n int) {\n\treturn sovOpenmetrics(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *MetricSet) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: MetricSet: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: MetricSet: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field MetricFamilies\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.MetricFamilies = append(m.MetricFamilies, &MetricFamily{})\n\t\t\tif err := m.MetricFamilies[len(m.MetricFamilies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *MetricFamily) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: MetricFamily: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: MetricFamily: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Name\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Name = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Type\", wireType)\n\t\t\t}\n\t\t\tm.Type = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Type |= MetricType(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Unit\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Unit = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Help\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Help = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Metrics\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Metrics = append(m.Metrics, &Metric{})\n\t\t\tif err := m.Metrics[len(m.Metrics)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Metric) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Metric: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Metric: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Labels\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Labels = append(m.Labels, &Label{})\n\t\t\tif err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field MetricPoints\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.MetricPoints = append(m.MetricPoints, &MetricPoint{})\n\t\t\tif err := m.MetricPoints[len(m.MetricPoints)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Label) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Label: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Label: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Name\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Name = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Value\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Value = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *MetricPoint) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: MetricPoint: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: MetricPoint: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field UnknownValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &UnknownValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_UnknownValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field GaugeValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &GaugeValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_GaugeValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field CounterValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &CounterValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_CounterValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field HistogramValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &HistogramValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_HistogramValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StateSetValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &StateSetValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_StateSetValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field InfoValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &InfoValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_InfoValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SummaryValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &SummaryValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &MetricPoint_SummaryValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 8:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Timestamp\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Timestamp == nil {\n\t\t\t\tm.Timestamp = &types.Timestamp{}\n\t\t\t}\n\t\t\tif err := m.Timestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *UnknownValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: UnknownValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: UnknownValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DoubleValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Value = &UnknownValue_DoubleValue{float64(math.Float64frombits(v))}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field IntValue\", wireType)\n\t\t\t}\n\t\t\tvar v int64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Value = &UnknownValue_IntValue{v}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GaugeValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GaugeValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GaugeValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DoubleValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Value = &GaugeValue_DoubleValue{float64(math.Float64frombits(v))}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field IntValue\", wireType)\n\t\t\t}\n\t\t\tvar v int64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Value = &GaugeValue_IntValue{v}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *CounterValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: CounterValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: CounterValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DoubleValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Total = &CounterValue_DoubleValue{float64(math.Float64frombits(v))}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field IntValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Total = &CounterValue_IntValue{v}\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Created\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Created == nil {\n\t\t\t\tm.Created = &types.Timestamp{}\n\t\t\t}\n\t\t\tif err := m.Created.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Exemplar\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Exemplar == nil {\n\t\t\t\tm.Exemplar = &Exemplar{}\n\t\t\t}\n\t\t\tif err := m.Exemplar.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *HistogramValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: HistogramValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: HistogramValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DoubleValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Sum = &HistogramValue_DoubleValue{float64(math.Float64frombits(v))}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field IntValue\", wireType)\n\t\t\t}\n\t\t\tvar v int64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Sum = &HistogramValue_IntValue{v}\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Count\", wireType)\n\t\t\t}\n\t\t\tm.Count = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Count |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Created\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Created == nil {\n\t\t\t\tm.Created = &types.Timestamp{}\n\t\t\t}\n\t\t\tif err := m.Created.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Buckets\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Buckets = append(m.Buckets, &HistogramValue_Bucket{})\n\t\t\tif err := m.Buckets[len(m.Buckets)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *HistogramValue_Bucket) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Bucket: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Bucket: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Count\", wireType)\n\t\t\t}\n\t\t\tm.Count = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Count |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field UpperBound\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.UpperBound = float64(math.Float64frombits(v))\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Exemplar\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Exemplar == nil {\n\t\t\t\tm.Exemplar = &Exemplar{}\n\t\t\t}\n\t\t\tif err := m.Exemplar.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Exemplar) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Exemplar: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Exemplar: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Value\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Value = float64(math.Float64frombits(v))\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Timestamp\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Timestamp == nil {\n\t\t\t\tm.Timestamp = &types.Timestamp{}\n\t\t\t}\n\t\t\tif err := m.Timestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Label\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Label = append(m.Label, &Label{})\n\t\t\tif err := m.Label[len(m.Label)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *StateSetValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: StateSetValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: StateSetValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field States\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.States = append(m.States, &StateSetValue_State{})\n\t\t\tif err := m.States[len(m.States)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *StateSetValue_State) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: State: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: State: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Enabled\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Enabled = bool(v != 0)\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Name\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Name = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *InfoValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: InfoValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: InfoValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Info\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Info = append(m.Info, &Label{})\n\t\t\tif err := m.Info[len(m.Info)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *SummaryValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: SummaryValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: SummaryValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DoubleValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Sum = &SummaryValue_DoubleValue{float64(math.Float64frombits(v))}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field IntValue\", wireType)\n\t\t\t}\n\t\t\tvar v int64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Sum = &SummaryValue_IntValue{v}\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Count\", wireType)\n\t\t\t}\n\t\t\tm.Count = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Count |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Created\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Created == nil {\n\t\t\t\tm.Created = &types.Timestamp{}\n\t\t\t}\n\t\t\tif err := m.Created.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Quantile\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Quantile = append(m.Quantile, &SummaryValue_Quantile{})\n\t\t\tif err := m.Quantile[len(m.Quantile)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *SummaryValue_Quantile) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Quantile: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Quantile: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Quantile\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Quantile = float64(math.Float64frombits(v))\n\t\tcase 2:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Value\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Value = float64(math.Float64frombits(v))\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipOpenmetrics(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipOpenmetrics(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tdepth := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowOpenmetrics\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowOpenmetrics\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthOpenmetrics\n\t\t\t}\n\t\t\tiNdEx += length\n\t\tcase 3:\n\t\t\tdepth++\n\t\tcase 4:\n\t\t\tif depth == 0 {\n\t\t\t\treturn 0, ErrUnexpectedEndOfGroupOpenmetrics\n\t\t\t}\n\t\t\tdepth--\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t\tif iNdEx < 0 {\n\t\t\treturn 0, ErrInvalidLengthOpenmetrics\n\t\t}\n\t\tif depth == 0 {\n\t\t\treturn iNdEx, nil\n\t\t}\n\t}\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nvar (\n\tErrInvalidLengthOpenmetrics        = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowOpenmetrics          = fmt.Errorf(\"proto: integer overflow\")\n\tErrUnexpectedEndOfGroupOpenmetrics = fmt.Errorf(\"proto: unexpected end of group\")\n)\n"
  },
  {
    "path": "internal/proto-gen/api_v2/metrics/otelspankind.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: otelspankind.proto\n\npackage metrics\n\nimport (\n\tfmt \"fmt\"\n\t_ \"github.com/gogo/protobuf/gogoproto\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\n// SpanKind is the type of span. Can be used to specify additional relationships between spans\n// in addition to a parent/child relationship.\ntype SpanKind int32\n\nconst (\n\t// Unspecified. Do NOT use as default.\n\t// Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED.\n\tSpanKind_SPAN_KIND_UNSPECIFIED SpanKind = 0\n\t// Indicates that the span represents an internal operation within an application,\n\t// as opposed to an operation happening at the boundaries. Default value.\n\tSpanKind_SPAN_KIND_INTERNAL SpanKind = 1\n\t// Indicates that the span covers server-side handling of an RPC or other\n\t// remote network request.\n\tSpanKind_SPAN_KIND_SERVER SpanKind = 2\n\t// Indicates that the span describes a request to some remote service.\n\tSpanKind_SPAN_KIND_CLIENT SpanKind = 3\n\t// Indicates that the span describes a producer sending a message to a broker.\n\t// Unlike CLIENT and SERVER, there is often no direct critical path latency relationship\n\t// between producer and consumer spans. A PRODUCER span ends when the message was accepted\n\t// by the broker while the logical processing of the message might span a much longer time.\n\tSpanKind_SPAN_KIND_PRODUCER SpanKind = 4\n\t// Indicates that the span describes consumer receiving a message from a broker.\n\t// Like the PRODUCER kind, there is often no direct critical path latency relationship\n\t// between producer and consumer spans.\n\tSpanKind_SPAN_KIND_CONSUMER SpanKind = 5\n)\n\nvar SpanKind_name = map[int32]string{\n\t0: \"SPAN_KIND_UNSPECIFIED\",\n\t1: \"SPAN_KIND_INTERNAL\",\n\t2: \"SPAN_KIND_SERVER\",\n\t3: \"SPAN_KIND_CLIENT\",\n\t4: \"SPAN_KIND_PRODUCER\",\n\t5: \"SPAN_KIND_CONSUMER\",\n}\n\nvar SpanKind_value = map[string]int32{\n\t\"SPAN_KIND_UNSPECIFIED\": 0,\n\t\"SPAN_KIND_INTERNAL\":    1,\n\t\"SPAN_KIND_SERVER\":      2,\n\t\"SPAN_KIND_CLIENT\":      3,\n\t\"SPAN_KIND_PRODUCER\":    4,\n\t\"SPAN_KIND_CONSUMER\":    5,\n}\n\nfunc (x SpanKind) String() string {\n\treturn proto.EnumName(SpanKind_name, int32(x))\n}\n\nfunc (SpanKind) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_77f837d0289d1179, []int{0}\n}\n\nfunc init() {\n\tproto.RegisterEnum(\"jaeger.api_v2.metrics.SpanKind\", SpanKind_name, SpanKind_value)\n}\n\nfunc init() { proto.RegisterFile(\"otelspankind.proto\", fileDescriptor_77f837d0289d1179) }\n\nvar fileDescriptor_77f837d0289d1179 = []byte{\n\t// 224 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xca, 0x2f, 0x49, 0xcd,\n\t0x29, 0x2e, 0x48, 0xcc, 0xcb, 0xce, 0xcc, 0x4b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12,\n\t0xcd, 0x4a, 0x4c, 0x4d, 0x4f, 0x2d, 0xd2, 0x4b, 0x2c, 0xc8, 0x8c, 0x2f, 0x33, 0xd2, 0xcb, 0x4d,\n\t0x2d, 0x29, 0xca, 0x4c, 0x2e, 0x96, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, 0xd0, 0x07, 0xb1,\n\t0x20, 0x8a, 0xb5, 0x66, 0x32, 0x72, 0x71, 0x04, 0x17, 0x24, 0xe6, 0x79, 0x67, 0xe6, 0xa5, 0x08,\n\t0x49, 0x72, 0x89, 0x06, 0x07, 0x38, 0xfa, 0xc5, 0x7b, 0x7b, 0xfa, 0xb9, 0xc4, 0x87, 0xfa, 0x05,\n\t0x07, 0xb8, 0x3a, 0x7b, 0xba, 0x79, 0xba, 0xba, 0x08, 0x30, 0x08, 0x89, 0x71, 0x09, 0x21, 0xa4,\n\t0x3c, 0xfd, 0x42, 0x5c, 0x83, 0xfc, 0x1c, 0x7d, 0x04, 0x18, 0x85, 0x44, 0xb8, 0x04, 0x10, 0xe2,\n\t0xc1, 0xae, 0x41, 0x61, 0xae, 0x41, 0x02, 0x4c, 0xa8, 0xa2, 0xce, 0x3e, 0x9e, 0xae, 0x7e, 0x21,\n\t0x02, 0xcc, 0xa8, 0x66, 0x04, 0x04, 0xf9, 0xbb, 0x84, 0x3a, 0xbb, 0x06, 0x09, 0xb0, 0xa0, 0x8a,\n\t0x3b, 0xfb, 0xfb, 0x05, 0x87, 0xfa, 0xba, 0x06, 0x09, 0xb0, 0x3a, 0x99, 0x9d, 0x78, 0x24, 0xc7,\n\t0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x5c, 0xf2, 0x99, 0xf9, 0x7a, 0x10, 0x9f,\n\t0x95, 0x14, 0x25, 0x26, 0x67, 0xe6, 0xa5, 0xa3, 0x79, 0x30, 0x8a, 0x1d, 0xca, 0x48, 0x62, 0x03,\n\t0x7b, 0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x87, 0x34, 0x50, 0x06, 0x1d, 0x01, 0x00, 0x00,\n}\n"
  },
  {
    "path": "internal/proto-gen/patch.sed",
    "content": "0,/import \"google\\/protobuf\\/.*.proto\";/{\ns|import \"google/protobuf/.*.proto\";|&\\\n\\\nimport \"gogoproto/gogo.proto\";\\\n\\\noption (gogoproto.marshaler_all) = true;\\\noption (gogoproto.unmarshaler_all) = true;\\\noption (gogoproto.sizer_all) = true;\\\n|\n}\n\ns|google.protobuf.Timestamp \\(.*\\);|google.protobuf.Timestamp \\1 \\\n  [\\\n  (gogoproto.nullable) = false,\\\n  (gogoproto.stdtime) = true\\\n  ];|g\n\ns|google.protobuf.Duration \\(.*\\);|google.protobuf.Duration \\1 \\\n  [\\\n  (gogoproto.nullable) = false,\\\n  (gogoproto.stdduration) = true\\\n  ];|g\n"
  },
  {
    "path": "internal/proto-gen/storage/v2/dependency_storage.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: dependency_storage.proto\n\npackage storage\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\t_ \"github.com/gogo/protobuf/gogoproto\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\t_ \"github.com/gogo/protobuf/types\"\n\tgithub_com_gogo_protobuf_types \"github.com/gogo/protobuf/types\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tio \"io\"\n\tmath \"math\"\n\tmath_bits \"math/bits\"\n\ttime \"time\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\nvar _ = time.Kitchen\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\ntype GetDependenciesRequest struct {\n\t// start_time is the start of the time interval to search for the dependencies.\n\tStartTime time.Time `protobuf:\"bytes,1,opt,name=start_time,json=startTime,proto3,stdtime\" json:\"start_time\"`\n\t// end_time is the end of the time interval to search for the dependencies.\n\tEndTime              time.Time `protobuf:\"bytes,2,opt,name=end_time,json=endTime,proto3,stdtime\" json:\"end_time\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *GetDependenciesRequest) Reset()         { *m = GetDependenciesRequest{} }\nfunc (m *GetDependenciesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetDependenciesRequest) ProtoMessage()    {}\nfunc (*GetDependenciesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_17393f3b58692e2b, []int{0}\n}\nfunc (m *GetDependenciesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetDependenciesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetDependenciesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetDependenciesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetDependenciesRequest.Merge(m, src)\n}\nfunc (m *GetDependenciesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetDependenciesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetDependenciesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetDependenciesRequest proto.InternalMessageInfo\n\nfunc (m *GetDependenciesRequest) GetStartTime() time.Time {\n\tif m != nil {\n\t\treturn m.StartTime\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *GetDependenciesRequest) GetEndTime() time.Time {\n\tif m != nil {\n\t\treturn m.EndTime\n\t}\n\treturn time.Time{}\n}\n\n// Dependency represents a relationship between two services.\ntype Dependency struct {\n\t// parent is the name of the caller service.\n\tParent string `protobuf:\"bytes,1,opt,name=parent,proto3\" json:\"parent,omitempty\"`\n\t// child is the name of the service being called.\n\tChild string `protobuf:\"bytes,2,opt,name=child,proto3\" json:\"child,omitempty\"`\n\t// call_count is the number of times the parent service called the child service.\n\tCallCount uint64 `protobuf:\"varint,3,opt,name=call_count,json=callCount,proto3\" json:\"call_count,omitempty\"`\n\t// source contains the origin from where the dependency was extracted.\n\tSource               string   `protobuf:\"bytes,4,opt,name=source,proto3\" json:\"source,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Dependency) Reset()         { *m = Dependency{} }\nfunc (m *Dependency) String() string { return proto.CompactTextString(m) }\nfunc (*Dependency) ProtoMessage()    {}\nfunc (*Dependency) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_17393f3b58692e2b, []int{1}\n}\nfunc (m *Dependency) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Dependency) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Dependency.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Dependency) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Dependency.Merge(m, src)\n}\nfunc (m *Dependency) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Dependency) XXX_DiscardUnknown() {\n\txxx_messageInfo_Dependency.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Dependency proto.InternalMessageInfo\n\nfunc (m *Dependency) GetParent() string {\n\tif m != nil {\n\t\treturn m.Parent\n\t}\n\treturn \"\"\n}\n\nfunc (m *Dependency) GetChild() string {\n\tif m != nil {\n\t\treturn m.Child\n\t}\n\treturn \"\"\n}\n\nfunc (m *Dependency) GetCallCount() uint64 {\n\tif m != nil {\n\t\treturn m.CallCount\n\t}\n\treturn 0\n}\n\nfunc (m *Dependency) GetSource() string {\n\tif m != nil {\n\t\treturn m.Source\n\t}\n\treturn \"\"\n}\n\ntype GetDependenciesResponse struct {\n\tDependencies         []*Dependency `protobuf:\"bytes,1,rep,name=dependencies,proto3\" json:\"dependencies,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}      `json:\"-\"`\n\tXXX_unrecognized     []byte        `json:\"-\"`\n\tXXX_sizecache        int32         `json:\"-\"`\n}\n\nfunc (m *GetDependenciesResponse) Reset()         { *m = GetDependenciesResponse{} }\nfunc (m *GetDependenciesResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetDependenciesResponse) ProtoMessage()    {}\nfunc (*GetDependenciesResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_17393f3b58692e2b, []int{2}\n}\nfunc (m *GetDependenciesResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetDependenciesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetDependenciesResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetDependenciesResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetDependenciesResponse.Merge(m, src)\n}\nfunc (m *GetDependenciesResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetDependenciesResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetDependenciesResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetDependenciesResponse proto.InternalMessageInfo\n\nfunc (m *GetDependenciesResponse) GetDependencies() []*Dependency {\n\tif m != nil {\n\t\treturn m.Dependencies\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*GetDependenciesRequest)(nil), \"jaeger.storage.v2.GetDependenciesRequest\")\n\tproto.RegisterType((*Dependency)(nil), \"jaeger.storage.v2.Dependency\")\n\tproto.RegisterType((*GetDependenciesResponse)(nil), \"jaeger.storage.v2.GetDependenciesResponse\")\n}\n\nfunc init() { proto.RegisterFile(\"dependency_storage.proto\", fileDescriptor_17393f3b58692e2b) }\n\nvar fileDescriptor_17393f3b58692e2b = []byte{\n\t// 349 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x51, 0xcd, 0x4e, 0xc2, 0x40,\n\t0x10, 0x76, 0x05, 0x81, 0x0e, 0x26, 0xea, 0x06, 0xb1, 0x69, 0xc2, 0x4f, 0x38, 0xa1, 0x87, 0x92,\n\t0xd4, 0x07, 0x30, 0x82, 0x89, 0xf7, 0xc6, 0x93, 0x31, 0x21, 0xa5, 0x1d, 0x0b, 0xa6, 0x74, 0xcb,\n\t0xee, 0xd6, 0x84, 0xc4, 0x87, 0xf0, 0x09, 0x7c, 0x1e, 0x8e, 0x3e, 0x81, 0x1a, 0x9e, 0xc4, 0x6c,\n\t0x97, 0x2a, 0x0a, 0x07, 0xbd, 0xcd, 0x7c, 0x33, 0xdf, 0xf7, 0xcd, 0xee, 0x07, 0x66, 0x80, 0x09,\n\t0xc6, 0x01, 0xc6, 0xfe, 0x7c, 0x28, 0x24, 0xe3, 0x5e, 0x88, 0x76, 0xc2, 0x99, 0x64, 0xf4, 0xe8,\n\t0xc1, 0xc3, 0x10, 0xb9, 0x9d, 0xa3, 0x8f, 0x8e, 0xd5, 0x0a, 0x19, 0x0b, 0x23, 0xec, 0x65, 0x0b,\n\t0xa3, 0xf4, 0xbe, 0x27, 0x27, 0x53, 0x14, 0xd2, 0x9b, 0x26, 0x9a, 0x63, 0xd5, 0x42, 0x16, 0xb2,\n\t0xac, 0xec, 0xa9, 0x4a, 0xa3, 0x9d, 0x17, 0x02, 0xf5, 0x6b, 0x94, 0x57, 0xb9, 0xd3, 0x04, 0x85,\n\t0x8b, 0xb3, 0x14, 0x85, 0xa4, 0x03, 0x00, 0x21, 0x3d, 0x2e, 0x87, 0x4a, 0xc9, 0x24, 0x6d, 0xd2,\n\t0xad, 0x3a, 0x96, 0xad, 0x6d, 0xec, 0xdc, 0xc6, 0xbe, 0xc9, 0x6d, 0xfa, 0x95, 0xc5, 0x5b, 0x6b,\n\t0xe7, 0xf9, 0xbd, 0x45, 0x5c, 0x23, 0xe3, 0xa9, 0x09, 0xbd, 0x80, 0x0a, 0xc6, 0x81, 0x96, 0xd8,\n\t0xfd, 0x87, 0x44, 0x19, 0xe3, 0x40, 0xe1, 0x9d, 0x19, 0xc0, 0xd7, 0x71, 0x73, 0x5a, 0x87, 0x52,\n\t0xe2, 0x71, 0x8c, 0x65, 0x76, 0x8f, 0xe1, 0xae, 0x3a, 0x5a, 0x83, 0x3d, 0x7f, 0x3c, 0x89, 0x82,\n\t0xcc, 0xc3, 0x70, 0x75, 0x43, 0x1b, 0x00, 0xbe, 0x17, 0x45, 0x43, 0x9f, 0xa5, 0xb1, 0x34, 0x0b,\n\t0x6d, 0xd2, 0x2d, 0xba, 0x86, 0x42, 0x06, 0x0a, 0x50, 0x62, 0x82, 0xa5, 0xdc, 0x47, 0xb3, 0xa8,\n\t0xc5, 0x74, 0xd7, 0xb9, 0x83, 0x93, 0x8d, 0x2f, 0x11, 0x09, 0x8b, 0x05, 0xd2, 0x4b, 0xd8, 0x0f,\n\t0xd6, 0x70, 0x93, 0xb4, 0x0b, 0xdd, 0xaa, 0xd3, 0xb0, 0x37, 0xf2, 0xb0, 0xbf, 0x8f, 0x76, 0x7f,\n\t0x50, 0x9c, 0x27, 0x38, 0x5c, 0x9b, 0xa1, 0x17, 0x20, 0xa7, 0x63, 0x38, 0xf8, 0xe5, 0x48, 0x4f,\n\t0xb7, 0x68, 0x6e, 0x0f, 0xca, 0x3a, 0xfb, 0xcb, 0xaa, 0x7e, 0x40, 0xff, 0x78, 0xb1, 0x6c, 0x92,\n\t0xd7, 0x65, 0x93, 0x7c, 0x2c, 0x9b, 0xe4, 0xb6, 0xbc, 0x62, 0x8c, 0x4a, 0x59, 0x18, 0xe7, 0x9f,\n\t0x01, 0x00, 0x00, 0xff, 0xff, 0xa9, 0xde, 0x4d, 0xdd, 0x73, 0x02, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// DependencyReaderClient is the client API for DependencyReader service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype DependencyReaderClient interface {\n\t// GetDependencies loads service dependencies from storage.\n\tGetDependencies(ctx context.Context, in *GetDependenciesRequest, opts ...grpc.CallOption) (*GetDependenciesResponse, error)\n}\n\ntype dependencyReaderClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewDependencyReaderClient(cc *grpc.ClientConn) DependencyReaderClient {\n\treturn &dependencyReaderClient{cc}\n}\n\nfunc (c *dependencyReaderClient) GetDependencies(ctx context.Context, in *GetDependenciesRequest, opts ...grpc.CallOption) (*GetDependenciesResponse, error) {\n\tout := new(GetDependenciesResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v2.DependencyReader/GetDependencies\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// DependencyReaderServer is the server API for DependencyReader service.\ntype DependencyReaderServer interface {\n\t// GetDependencies loads service dependencies from storage.\n\tGetDependencies(context.Context, *GetDependenciesRequest) (*GetDependenciesResponse, error)\n}\n\n// UnimplementedDependencyReaderServer can be embedded to have forward compatible implementations.\ntype UnimplementedDependencyReaderServer struct {\n}\n\nfunc (*UnimplementedDependencyReaderServer) GetDependencies(ctx context.Context, req *GetDependenciesRequest) (*GetDependenciesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetDependencies not implemented\")\n}\n\nfunc RegisterDependencyReaderServer(s *grpc.Server, srv DependencyReaderServer) {\n\ts.RegisterService(&_DependencyReader_serviceDesc, srv)\n}\n\nfunc _DependencyReader_GetDependencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetDependenciesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DependencyReaderServer).GetDependencies(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v2.DependencyReader/GetDependencies\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DependencyReaderServer).GetDependencies(ctx, req.(*GetDependenciesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _DependencyReader_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v2.DependencyReader\",\n\tHandlerType: (*DependencyReaderServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetDependencies\",\n\t\t\tHandler:    _DependencyReader_GetDependencies_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"dependency_storage.proto\",\n}\n\nfunc (m *GetDependenciesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetDependenciesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetDependenciesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tn1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):])\n\tif err1 != nil {\n\t\treturn 0, err1\n\t}\n\ti -= n1\n\ti = encodeVarintDependencyStorage(dAtA, i, uint64(n1))\n\ti--\n\tdAtA[i] = 0x12\n\tn2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):])\n\tif err2 != nil {\n\t\treturn 0, err2\n\t}\n\ti -= n2\n\ti = encodeVarintDependencyStorage(dAtA, i, uint64(n2))\n\ti--\n\tdAtA[i] = 0xa\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Dependency) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Dependency) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Dependency) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Source) > 0 {\n\t\ti -= len(m.Source)\n\t\tcopy(dAtA[i:], m.Source)\n\t\ti = encodeVarintDependencyStorage(dAtA, i, uint64(len(m.Source)))\n\t\ti--\n\t\tdAtA[i] = 0x22\n\t}\n\tif m.CallCount != 0 {\n\t\ti = encodeVarintDependencyStorage(dAtA, i, uint64(m.CallCount))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif len(m.Child) > 0 {\n\t\ti -= len(m.Child)\n\t\tcopy(dAtA[i:], m.Child)\n\t\ti = encodeVarintDependencyStorage(dAtA, i, uint64(len(m.Child)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Parent) > 0 {\n\t\ti -= len(m.Parent)\n\t\tcopy(dAtA[i:], m.Parent)\n\t\ti = encodeVarintDependencyStorage(dAtA, i, uint64(len(m.Parent)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetDependenciesResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetDependenciesResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetDependenciesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Dependencies) > 0 {\n\t\tfor iNdEx := len(m.Dependencies) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Dependencies[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintDependencyStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc encodeVarintDependencyStorage(dAtA []byte, offset int, v uint64) int {\n\toffset -= sovDependencyStorage(v)\n\tbase := offset\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn base\n}\nfunc (m *GetDependenciesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime)\n\tn += 1 + l + sovDependencyStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime)\n\tn += 1 + l + sovDependencyStorage(uint64(l))\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Dependency) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Parent)\n\tif l > 0 {\n\t\tn += 1 + l + sovDependencyStorage(uint64(l))\n\t}\n\tl = len(m.Child)\n\tif l > 0 {\n\t\tn += 1 + l + sovDependencyStorage(uint64(l))\n\t}\n\tif m.CallCount != 0 {\n\t\tn += 1 + sovDependencyStorage(uint64(m.CallCount))\n\t}\n\tl = len(m.Source)\n\tif l > 0 {\n\t\tn += 1 + l + sovDependencyStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetDependenciesResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Dependencies) > 0 {\n\t\tfor _, e := range m.Dependencies {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovDependencyStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc sovDependencyStorage(x uint64) (n int) {\n\treturn (math_bits.Len64(x|1) + 6) / 7\n}\nfunc sozDependencyStorage(x uint64) (n int) {\n\treturn sovDependencyStorage(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *GetDependenciesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field EndTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipDependencyStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Dependency) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Dependency: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Dependency: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Parent\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Parent = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Child\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Child = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field CallCount\", wireType)\n\t\t\t}\n\t\t\tm.CallCount = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.CallCount |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Source\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Source = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipDependencyStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetDependenciesResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Dependencies\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Dependencies = append(m.Dependencies, &Dependency{})\n\t\t\tif err := m.Dependencies[len(m.Dependencies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipDependencyStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipDependencyStorage(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tdepth := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowDependencyStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowDependencyStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthDependencyStorage\n\t\t\t}\n\t\t\tiNdEx += length\n\t\tcase 3:\n\t\t\tdepth++\n\t\tcase 4:\n\t\t\tif depth == 0 {\n\t\t\t\treturn 0, ErrUnexpectedEndOfGroupDependencyStorage\n\t\t\t}\n\t\t\tdepth--\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t\tif iNdEx < 0 {\n\t\t\treturn 0, ErrInvalidLengthDependencyStorage\n\t\t}\n\t\tif depth == 0 {\n\t\t\treturn iNdEx, nil\n\t\t}\n\t}\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nvar (\n\tErrInvalidLengthDependencyStorage        = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowDependencyStorage          = fmt.Errorf(\"proto: integer overflow\")\n\tErrUnexpectedEndOfGroupDependencyStorage = fmt.Errorf(\"proto: unexpected end of group\")\n)\n"
  },
  {
    "path": "internal/proto-gen/storage/v2/trace_storage.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: trace_storage.proto\n\npackage storage\n\nimport (\n\tcontext \"context\"\n\tencoding_binary \"encoding/binary\"\n\tfmt \"fmt\"\n\t_ \"github.com/gogo/protobuf/gogoproto\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\t_ \"github.com/gogo/protobuf/types\"\n\tgithub_com_gogo_protobuf_types \"github.com/gogo/protobuf/types\"\n\tv1 \"github.com/jaegertracing/jaeger/internal/jptrace\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tio \"io\"\n\tmath \"math\"\n\tmath_bits \"math/bits\"\n\ttime \"time\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\nvar _ = time.Kitchen\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\n// GetTraceParams represents the query for a single trace from the storage backend.\ntype GetTraceParams struct {\n\t// trace_id is a 16 byte array containing the unique identifier for the trace to query.\n\tTraceId []byte `protobuf:\"bytes,1,opt,name=trace_id,json=traceId,proto3\" json:\"trace_id,omitempty\"`\n\t// start_time is the start of the time interval to search for the trace_id.\n\t//\n\t// This field is optional.\n\tStartTime time.Time `protobuf:\"bytes,2,opt,name=start_time,json=startTime,proto3,stdtime\" json:\"start_time\"`\n\t// end_time is the end of the time interval to search for the trace_id.\n\t//\n\t// This field is optional.\n\tEndTime              time.Time `protobuf:\"bytes,3,opt,name=end_time,json=endTime,proto3,stdtime\" json:\"end_time\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *GetTraceParams) Reset()         { *m = GetTraceParams{} }\nfunc (m *GetTraceParams) String() string { return proto.CompactTextString(m) }\nfunc (*GetTraceParams) ProtoMessage()    {}\nfunc (*GetTraceParams) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{0}\n}\nfunc (m *GetTraceParams) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetTraceParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetTraceParams.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetTraceParams) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetTraceParams.Merge(m, src)\n}\nfunc (m *GetTraceParams) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetTraceParams) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetTraceParams.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetTraceParams proto.InternalMessageInfo\n\nfunc (m *GetTraceParams) GetTraceId() []byte {\n\tif m != nil {\n\t\treturn m.TraceId\n\t}\n\treturn nil\n}\n\nfunc (m *GetTraceParams) GetStartTime() time.Time {\n\tif m != nil {\n\t\treturn m.StartTime\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *GetTraceParams) GetEndTime() time.Time {\n\tif m != nil {\n\t\treturn m.EndTime\n\t}\n\treturn time.Time{}\n}\n\n// GetTracesRequest represents a request to retrieve multiple traces.\ntype GetTracesRequest struct {\n\tQuery                []*GetTraceParams `protobuf:\"bytes,1,rep,name=query,proto3\" json:\"query,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}          `json:\"-\"`\n\tXXX_unrecognized     []byte            `json:\"-\"`\n\tXXX_sizecache        int32             `json:\"-\"`\n}\n\nfunc (m *GetTracesRequest) Reset()         { *m = GetTracesRequest{} }\nfunc (m *GetTracesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetTracesRequest) ProtoMessage()    {}\nfunc (*GetTracesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{1}\n}\nfunc (m *GetTracesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetTracesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetTracesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetTracesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetTracesRequest.Merge(m, src)\n}\nfunc (m *GetTracesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetTracesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetTracesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetTracesRequest proto.InternalMessageInfo\n\nfunc (m *GetTracesRequest) GetQuery() []*GetTraceParams {\n\tif m != nil {\n\t\treturn m.Query\n\t}\n\treturn nil\n}\n\n// GetServicesRequest represents a request to get service names.\ntype GetServicesRequest struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetServicesRequest) Reset()         { *m = GetServicesRequest{} }\nfunc (m *GetServicesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetServicesRequest) ProtoMessage()    {}\nfunc (*GetServicesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{2}\n}\nfunc (m *GetServicesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetServicesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetServicesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetServicesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetServicesRequest.Merge(m, src)\n}\nfunc (m *GetServicesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetServicesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetServicesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetServicesRequest proto.InternalMessageInfo\n\n// GetServicesResponse represents the response for GetServicesRequest.\ntype GetServicesResponse struct {\n\tServices             []string `protobuf:\"bytes,1,rep,name=services,proto3\" json:\"services,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetServicesResponse) Reset()         { *m = GetServicesResponse{} }\nfunc (m *GetServicesResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetServicesResponse) ProtoMessage()    {}\nfunc (*GetServicesResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{3}\n}\nfunc (m *GetServicesResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetServicesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetServicesResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetServicesResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetServicesResponse.Merge(m, src)\n}\nfunc (m *GetServicesResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetServicesResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetServicesResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetServicesResponse proto.InternalMessageInfo\n\nfunc (m *GetServicesResponse) GetServices() []string {\n\tif m != nil {\n\t\treturn m.Services\n\t}\n\treturn nil\n}\n\n// GetOperationsRequest represents a request to get operation names.\ntype GetOperationsRequest struct {\n\t// service is the name of the service for which to get operation names.\n\t//\n\t// This field is required.\n\tService string `protobuf:\"bytes,1,opt,name=service,proto3\" json:\"service,omitempty\"`\n\t// span_kind is the type of span which is used to distinguish between\n\t// spans generated in a particular context.\n\t//\n\t// This field is optional.\n\tSpanKind             string   `protobuf:\"bytes,2,opt,name=span_kind,json=spanKind,proto3\" json:\"span_kind,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetOperationsRequest) Reset()         { *m = GetOperationsRequest{} }\nfunc (m *GetOperationsRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetOperationsRequest) ProtoMessage()    {}\nfunc (*GetOperationsRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{4}\n}\nfunc (m *GetOperationsRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetOperationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetOperationsRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetOperationsRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetOperationsRequest.Merge(m, src)\n}\nfunc (m *GetOperationsRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetOperationsRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetOperationsRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetOperationsRequest proto.InternalMessageInfo\n\nfunc (m *GetOperationsRequest) GetService() string {\n\tif m != nil {\n\t\treturn m.Service\n\t}\n\treturn \"\"\n}\n\nfunc (m *GetOperationsRequest) GetSpanKind() string {\n\tif m != nil {\n\t\treturn m.SpanKind\n\t}\n\treturn \"\"\n}\n\n// Operation contains information about an operation for a given service.\ntype Operation struct {\n\tName                 string   `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tSpanKind             string   `protobuf:\"bytes,2,opt,name=span_kind,json=spanKind,proto3\" json:\"span_kind,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Operation) Reset()         { *m = Operation{} }\nfunc (m *Operation) String() string { return proto.CompactTextString(m) }\nfunc (*Operation) ProtoMessage()    {}\nfunc (*Operation) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{5}\n}\nfunc (m *Operation) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Operation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Operation.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Operation) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Operation.Merge(m, src)\n}\nfunc (m *Operation) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Operation) XXX_DiscardUnknown() {\n\txxx_messageInfo_Operation.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Operation proto.InternalMessageInfo\n\nfunc (m *Operation) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Operation) GetSpanKind() string {\n\tif m != nil {\n\t\treturn m.SpanKind\n\t}\n\treturn \"\"\n}\n\n// GetOperationsResponse represents the response for GetOperationsRequest.\ntype GetOperationsResponse struct {\n\tOperations           []*Operation `protobuf:\"bytes,1,rep,name=operations,proto3\" json:\"operations,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}     `json:\"-\"`\n\tXXX_unrecognized     []byte       `json:\"-\"`\n\tXXX_sizecache        int32        `json:\"-\"`\n}\n\nfunc (m *GetOperationsResponse) Reset()         { *m = GetOperationsResponse{} }\nfunc (m *GetOperationsResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetOperationsResponse) ProtoMessage()    {}\nfunc (*GetOperationsResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{6}\n}\nfunc (m *GetOperationsResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetOperationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetOperationsResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetOperationsResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetOperationsResponse.Merge(m, src)\n}\nfunc (m *GetOperationsResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetOperationsResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetOperationsResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetOperationsResponse proto.InternalMessageInfo\n\nfunc (m *GetOperationsResponse) GetOperations() []*Operation {\n\tif m != nil {\n\t\treturn m.Operations\n\t}\n\treturn nil\n}\n\n// KeyValue and all its associated types are copied from opentelemetry-proto/common/v1/common.proto\n// (https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto).\n// This type is used to store attributes in traces.\ntype KeyValue struct {\n\tKey                  string    `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tValue                *AnyValue `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *KeyValue) Reset()         { *m = KeyValue{} }\nfunc (m *KeyValue) String() string { return proto.CompactTextString(m) }\nfunc (*KeyValue) ProtoMessage()    {}\nfunc (*KeyValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{7}\n}\nfunc (m *KeyValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *KeyValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_KeyValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *KeyValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_KeyValue.Merge(m, src)\n}\nfunc (m *KeyValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *KeyValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_KeyValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_KeyValue proto.InternalMessageInfo\n\nfunc (m *KeyValue) GetKey() string {\n\tif m != nil {\n\t\treturn m.Key\n\t}\n\treturn \"\"\n}\n\nfunc (m *KeyValue) GetValue() *AnyValue {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\ntype AnyValue struct {\n\t// Types that are valid to be assigned to Value:\n\t//\t*AnyValue_StringValue\n\t//\t*AnyValue_BoolValue\n\t//\t*AnyValue_IntValue\n\t//\t*AnyValue_DoubleValue\n\t//\t*AnyValue_ArrayValue\n\t//\t*AnyValue_KvlistValue\n\t//\t*AnyValue_BytesValue\n\tValue                isAnyValue_Value `protobuf_oneof:\"value\"`\n\tXXX_NoUnkeyedLiteral struct{}         `json:\"-\"`\n\tXXX_unrecognized     []byte           `json:\"-\"`\n\tXXX_sizecache        int32            `json:\"-\"`\n}\n\nfunc (m *AnyValue) Reset()         { *m = AnyValue{} }\nfunc (m *AnyValue) String() string { return proto.CompactTextString(m) }\nfunc (*AnyValue) ProtoMessage()    {}\nfunc (*AnyValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{8}\n}\nfunc (m *AnyValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *AnyValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_AnyValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *AnyValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_AnyValue.Merge(m, src)\n}\nfunc (m *AnyValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *AnyValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_AnyValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_AnyValue proto.InternalMessageInfo\n\ntype isAnyValue_Value interface {\n\tisAnyValue_Value()\n\tMarshalTo([]byte) (int, error)\n\tSize() int\n}\n\ntype AnyValue_StringValue struct {\n\tStringValue string `protobuf:\"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof\" json:\"string_value,omitempty\"`\n}\ntype AnyValue_BoolValue struct {\n\tBoolValue bool `protobuf:\"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof\" json:\"bool_value,omitempty\"`\n}\ntype AnyValue_IntValue struct {\n\tIntValue int64 `protobuf:\"varint,3,opt,name=int_value,json=intValue,proto3,oneof\" json:\"int_value,omitempty\"`\n}\ntype AnyValue_DoubleValue struct {\n\tDoubleValue float64 `protobuf:\"fixed64,4,opt,name=double_value,json=doubleValue,proto3,oneof\" json:\"double_value,omitempty\"`\n}\ntype AnyValue_ArrayValue struct {\n\tArrayValue *ArrayValue `protobuf:\"bytes,5,opt,name=array_value,json=arrayValue,proto3,oneof\" json:\"array_value,omitempty\"`\n}\ntype AnyValue_KvlistValue struct {\n\tKvlistValue *KeyValueList `protobuf:\"bytes,6,opt,name=kvlist_value,json=kvlistValue,proto3,oneof\" json:\"kvlist_value,omitempty\"`\n}\ntype AnyValue_BytesValue struct {\n\tBytesValue []byte `protobuf:\"bytes,7,opt,name=bytes_value,json=bytesValue,proto3,oneof\" json:\"bytes_value,omitempty\"`\n}\n\nfunc (*AnyValue_StringValue) isAnyValue_Value() {}\nfunc (*AnyValue_BoolValue) isAnyValue_Value()   {}\nfunc (*AnyValue_IntValue) isAnyValue_Value()    {}\nfunc (*AnyValue_DoubleValue) isAnyValue_Value() {}\nfunc (*AnyValue_ArrayValue) isAnyValue_Value()  {}\nfunc (*AnyValue_KvlistValue) isAnyValue_Value() {}\nfunc (*AnyValue_BytesValue) isAnyValue_Value()  {}\n\nfunc (m *AnyValue) GetValue() isAnyValue_Value {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\nfunc (m *AnyValue) GetStringValue() string {\n\tif x, ok := m.GetValue().(*AnyValue_StringValue); ok {\n\t\treturn x.StringValue\n\t}\n\treturn \"\"\n}\n\nfunc (m *AnyValue) GetBoolValue() bool {\n\tif x, ok := m.GetValue().(*AnyValue_BoolValue); ok {\n\t\treturn x.BoolValue\n\t}\n\treturn false\n}\n\nfunc (m *AnyValue) GetIntValue() int64 {\n\tif x, ok := m.GetValue().(*AnyValue_IntValue); ok {\n\t\treturn x.IntValue\n\t}\n\treturn 0\n}\n\nfunc (m *AnyValue) GetDoubleValue() float64 {\n\tif x, ok := m.GetValue().(*AnyValue_DoubleValue); ok {\n\t\treturn x.DoubleValue\n\t}\n\treturn 0\n}\n\nfunc (m *AnyValue) GetArrayValue() *ArrayValue {\n\tif x, ok := m.GetValue().(*AnyValue_ArrayValue); ok {\n\t\treturn x.ArrayValue\n\t}\n\treturn nil\n}\n\nfunc (m *AnyValue) GetKvlistValue() *KeyValueList {\n\tif x, ok := m.GetValue().(*AnyValue_KvlistValue); ok {\n\t\treturn x.KvlistValue\n\t}\n\treturn nil\n}\n\nfunc (m *AnyValue) GetBytesValue() []byte {\n\tif x, ok := m.GetValue().(*AnyValue_BytesValue); ok {\n\t\treturn x.BytesValue\n\t}\n\treturn nil\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*AnyValue) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*AnyValue_StringValue)(nil),\n\t\t(*AnyValue_BoolValue)(nil),\n\t\t(*AnyValue_IntValue)(nil),\n\t\t(*AnyValue_DoubleValue)(nil),\n\t\t(*AnyValue_ArrayValue)(nil),\n\t\t(*AnyValue_KvlistValue)(nil),\n\t\t(*AnyValue_BytesValue)(nil),\n\t}\n}\n\ntype KeyValueList struct {\n\tValues               []*KeyValue `protobuf:\"bytes,1,rep,name=values,proto3\" json:\"values,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}    `json:\"-\"`\n\tXXX_unrecognized     []byte      `json:\"-\"`\n\tXXX_sizecache        int32       `json:\"-\"`\n}\n\nfunc (m *KeyValueList) Reset()         { *m = KeyValueList{} }\nfunc (m *KeyValueList) String() string { return proto.CompactTextString(m) }\nfunc (*KeyValueList) ProtoMessage()    {}\nfunc (*KeyValueList) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{9}\n}\nfunc (m *KeyValueList) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *KeyValueList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_KeyValueList.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *KeyValueList) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_KeyValueList.Merge(m, src)\n}\nfunc (m *KeyValueList) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *KeyValueList) XXX_DiscardUnknown() {\n\txxx_messageInfo_KeyValueList.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_KeyValueList proto.InternalMessageInfo\n\nfunc (m *KeyValueList) GetValues() []*KeyValue {\n\tif m != nil {\n\t\treturn m.Values\n\t}\n\treturn nil\n}\n\ntype ArrayValue struct {\n\tValues               []*AnyValue `protobuf:\"bytes,1,rep,name=values,proto3\" json:\"values,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}    `json:\"-\"`\n\tXXX_unrecognized     []byte      `json:\"-\"`\n\tXXX_sizecache        int32       `json:\"-\"`\n}\n\nfunc (m *ArrayValue) Reset()         { *m = ArrayValue{} }\nfunc (m *ArrayValue) String() string { return proto.CompactTextString(m) }\nfunc (*ArrayValue) ProtoMessage()    {}\nfunc (*ArrayValue) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{10}\n}\nfunc (m *ArrayValue) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *ArrayValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_ArrayValue.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *ArrayValue) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ArrayValue.Merge(m, src)\n}\nfunc (m *ArrayValue) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *ArrayValue) XXX_DiscardUnknown() {\n\txxx_messageInfo_ArrayValue.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ArrayValue proto.InternalMessageInfo\n\nfunc (m *ArrayValue) GetValues() []*AnyValue {\n\tif m != nil {\n\t\treturn m.Values\n\t}\n\treturn nil\n}\n\n// TraceQueryParameters contains query parameters to find traces. For a detailed\n// definition of each field in this message, refer to `TraceQueryParameters` in `jaeger.api_v3`\n// (https://github.com/jaegertracing/jaeger-idl/blob/main/proto/api_v3/query_service.proto).\ntype TraceQueryParameters struct {\n\tServiceName          string        `protobuf:\"bytes,1,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\tOperationName        string        `protobuf:\"bytes,2,opt,name=operation_name,json=operationName,proto3\" json:\"operation_name,omitempty\"`\n\tAttributes           []*KeyValue   `protobuf:\"bytes,3,rep,name=attributes,proto3\" json:\"attributes,omitempty\"`\n\tStartTimeMin         time.Time     `protobuf:\"bytes,4,opt,name=start_time_min,json=startTimeMin,proto3,stdtime\" json:\"start_time_min\"`\n\tStartTimeMax         time.Time     `protobuf:\"bytes,5,opt,name=start_time_max,json=startTimeMax,proto3,stdtime\" json:\"start_time_max\"`\n\tDurationMin          time.Duration `protobuf:\"bytes,6,opt,name=duration_min,json=durationMin,proto3,stdduration\" json:\"duration_min\"`\n\tDurationMax          time.Duration `protobuf:\"bytes,7,opt,name=duration_max,json=durationMax,proto3,stdduration\" json:\"duration_max\"`\n\tSearchDepth          int32         `protobuf:\"varint,8,opt,name=search_depth,json=searchDepth,proto3\" json:\"search_depth,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}      `json:\"-\"`\n\tXXX_unrecognized     []byte        `json:\"-\"`\n\tXXX_sizecache        int32         `json:\"-\"`\n}\n\nfunc (m *TraceQueryParameters) Reset()         { *m = TraceQueryParameters{} }\nfunc (m *TraceQueryParameters) String() string { return proto.CompactTextString(m) }\nfunc (*TraceQueryParameters) ProtoMessage()    {}\nfunc (*TraceQueryParameters) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{11}\n}\nfunc (m *TraceQueryParameters) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *TraceQueryParameters) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_TraceQueryParameters.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *TraceQueryParameters) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_TraceQueryParameters.Merge(m, src)\n}\nfunc (m *TraceQueryParameters) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *TraceQueryParameters) XXX_DiscardUnknown() {\n\txxx_messageInfo_TraceQueryParameters.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_TraceQueryParameters proto.InternalMessageInfo\n\nfunc (m *TraceQueryParameters) GetServiceName() string {\n\tif m != nil {\n\t\treturn m.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (m *TraceQueryParameters) GetOperationName() string {\n\tif m != nil {\n\t\treturn m.OperationName\n\t}\n\treturn \"\"\n}\n\nfunc (m *TraceQueryParameters) GetAttributes() []*KeyValue {\n\tif m != nil {\n\t\treturn m.Attributes\n\t}\n\treturn nil\n}\n\nfunc (m *TraceQueryParameters) GetStartTimeMin() time.Time {\n\tif m != nil {\n\t\treturn m.StartTimeMin\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *TraceQueryParameters) GetStartTimeMax() time.Time {\n\tif m != nil {\n\t\treturn m.StartTimeMax\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *TraceQueryParameters) GetDurationMin() time.Duration {\n\tif m != nil {\n\t\treturn m.DurationMin\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetDurationMax() time.Duration {\n\tif m != nil {\n\t\treturn m.DurationMax\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetSearchDepth() int32 {\n\tif m != nil {\n\t\treturn m.SearchDepth\n\t}\n\treturn 0\n}\n\n// FindTracesRequest represents a request to find traces.\n// It can be used to retrieve the traces (FindTraces) or simply\n// the trace IDs (FindTraceIDs).\ntype FindTracesRequest struct {\n\tQuery                *TraceQueryParameters `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}              `json:\"-\"`\n\tXXX_unrecognized     []byte                `json:\"-\"`\n\tXXX_sizecache        int32                 `json:\"-\"`\n}\n\nfunc (m *FindTracesRequest) Reset()         { *m = FindTracesRequest{} }\nfunc (m *FindTracesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*FindTracesRequest) ProtoMessage()    {}\nfunc (*FindTracesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{12}\n}\nfunc (m *FindTracesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FindTracesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FindTracesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FindTracesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FindTracesRequest.Merge(m, src)\n}\nfunc (m *FindTracesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FindTracesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_FindTracesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FindTracesRequest proto.InternalMessageInfo\n\nfunc (m *FindTracesRequest) GetQuery() *TraceQueryParameters {\n\tif m != nil {\n\t\treturn m.Query\n\t}\n\treturn nil\n}\n\n// FoundTraceID is a wrapper around trace ID returned from FindTraceIDs\n// with an optional time range that may be used in GetTraces calls.\n//\n// The time range is provided as an optimization hint for some storage backends\n// that can perform more efficient queries when they know the approximate time range.\n// The value should not be used for precise time-based filtering or assumptions.\n// It is meant as a rough boundary and may not be populated in all cases.\ntype FoundTraceID struct {\n\tTraceId              []byte    `protobuf:\"bytes,1,opt,name=trace_id,json=traceId,proto3\" json:\"trace_id,omitempty\"`\n\tStart                time.Time `protobuf:\"bytes,2,opt,name=start,proto3,stdtime\" json:\"start\"`\n\tEnd                  time.Time `protobuf:\"bytes,3,opt,name=end,proto3,stdtime\" json:\"end\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *FoundTraceID) Reset()         { *m = FoundTraceID{} }\nfunc (m *FoundTraceID) String() string { return proto.CompactTextString(m) }\nfunc (*FoundTraceID) ProtoMessage()    {}\nfunc (*FoundTraceID) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{13}\n}\nfunc (m *FoundTraceID) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FoundTraceID) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FoundTraceID.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FoundTraceID) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FoundTraceID.Merge(m, src)\n}\nfunc (m *FoundTraceID) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FoundTraceID) XXX_DiscardUnknown() {\n\txxx_messageInfo_FoundTraceID.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FoundTraceID proto.InternalMessageInfo\n\nfunc (m *FoundTraceID) GetTraceId() []byte {\n\tif m != nil {\n\t\treturn m.TraceId\n\t}\n\treturn nil\n}\n\nfunc (m *FoundTraceID) GetStart() time.Time {\n\tif m != nil {\n\t\treturn m.Start\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *FoundTraceID) GetEnd() time.Time {\n\tif m != nil {\n\t\treturn m.End\n\t}\n\treturn time.Time{}\n}\n\n// FindTraceIDsResponse represents the response for FindTracesRequest.\ntype FindTraceIDsResponse struct {\n\tTraceIds             []*FoundTraceID `protobuf:\"bytes,1,rep,name=trace_ids,json=traceIds,proto3\" json:\"trace_ids,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *FindTraceIDsResponse) Reset()         { *m = FindTraceIDsResponse{} }\nfunc (m *FindTraceIDsResponse) String() string { return proto.CompactTextString(m) }\nfunc (*FindTraceIDsResponse) ProtoMessage()    {}\nfunc (*FindTraceIDsResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_3441c0fd9397413c, []int{14}\n}\nfunc (m *FindTraceIDsResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FindTraceIDsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FindTraceIDsResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FindTraceIDsResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FindTraceIDsResponse.Merge(m, src)\n}\nfunc (m *FindTraceIDsResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FindTraceIDsResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_FindTraceIDsResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FindTraceIDsResponse proto.InternalMessageInfo\n\nfunc (m *FindTraceIDsResponse) GetTraceIds() []*FoundTraceID {\n\tif m != nil {\n\t\treturn m.TraceIds\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*GetTraceParams)(nil), \"jaeger.storage.v2.GetTraceParams\")\n\tproto.RegisterType((*GetTracesRequest)(nil), \"jaeger.storage.v2.GetTracesRequest\")\n\tproto.RegisterType((*GetServicesRequest)(nil), \"jaeger.storage.v2.GetServicesRequest\")\n\tproto.RegisterType((*GetServicesResponse)(nil), \"jaeger.storage.v2.GetServicesResponse\")\n\tproto.RegisterType((*GetOperationsRequest)(nil), \"jaeger.storage.v2.GetOperationsRequest\")\n\tproto.RegisterType((*Operation)(nil), \"jaeger.storage.v2.Operation\")\n\tproto.RegisterType((*GetOperationsResponse)(nil), \"jaeger.storage.v2.GetOperationsResponse\")\n\tproto.RegisterType((*KeyValue)(nil), \"jaeger.storage.v2.KeyValue\")\n\tproto.RegisterType((*AnyValue)(nil), \"jaeger.storage.v2.AnyValue\")\n\tproto.RegisterType((*KeyValueList)(nil), \"jaeger.storage.v2.KeyValueList\")\n\tproto.RegisterType((*ArrayValue)(nil), \"jaeger.storage.v2.ArrayValue\")\n\tproto.RegisterType((*TraceQueryParameters)(nil), \"jaeger.storage.v2.TraceQueryParameters\")\n\tproto.RegisterType((*FindTracesRequest)(nil), \"jaeger.storage.v2.FindTracesRequest\")\n\tproto.RegisterType((*FoundTraceID)(nil), \"jaeger.storage.v2.FoundTraceID\")\n\tproto.RegisterType((*FindTraceIDsResponse)(nil), \"jaeger.storage.v2.FindTraceIDsResponse\")\n}\n\nfunc init() { proto.RegisterFile(\"trace_storage.proto\", fileDescriptor_3441c0fd9397413c) }\n\nvar fileDescriptor_3441c0fd9397413c = []byte{\n\t// 968 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdd, 0x72, 0xdb, 0x44,\n\t0x14, 0xb6, 0xea, 0x3a, 0x96, 0x8e, 0xd4, 0x4c, 0xbb, 0x75, 0x67, 0x5c, 0x87, 0xc6, 0x8e, 0x4a,\n\t0x89, 0xaf, 0x64, 0x92, 0xce, 0xc0, 0x0c, 0x94, 0x81, 0xa6, 0x9e, 0xfc, 0x10, 0x4a, 0x41, 0x04,\n\t0x2e, 0xb8, 0xa8, 0x58, 0x57, 0x8b, 0x2b, 0x62, 0xaf, 0x5c, 0xed, 0xda, 0x63, 0xbf, 0x05, 0x97,\n\t0xdc, 0xf0, 0x10, 0xbc, 0x45, 0x2e, 0x79, 0x02, 0x60, 0x72, 0xc5, 0x13, 0x70, 0xcd, 0xec, 0x8f,\n\t0x64, 0xd9, 0xd1, 0x38, 0xf1, 0xdd, 0xea, 0xd3, 0x77, 0xbe, 0xf3, 0xb3, 0x67, 0xcf, 0x81, 0xfb,\n\t0x3c, 0xc1, 0x6f, 0x48, 0xc0, 0x78, 0x9c, 0xe0, 0x3e, 0xf1, 0x46, 0x49, 0xcc, 0x63, 0x74, 0xef,\n\t0x17, 0x4c, 0xfa, 0x24, 0xf1, 0x52, 0x74, 0xb2, 0xdf, 0xd8, 0xee, 0xc7, 0x71, 0x7f, 0x40, 0x3a,\n\t0x92, 0xd0, 0x1b, 0xff, 0xdc, 0x09, 0xc7, 0x09, 0xe6, 0x51, 0x4c, 0x95, 0x49, 0xa3, 0xd6, 0x8f,\n\t0xfb, 0xb1, 0x3c, 0x76, 0xc4, 0x49, 0xa3, 0xcd, 0x65, 0x2b, 0x1e, 0x0d, 0x09, 0xe3, 0x78, 0x38,\n\t0xd2, 0x84, 0x76, 0x3c, 0x22, 0x94, 0x93, 0x01, 0x19, 0x12, 0x9e, 0xcc, 0x14, 0xaf, 0x23, 0x43,\n\t0xea, 0x4c, 0xf6, 0xd4, 0x41, 0x31, 0xdd, 0x3f, 0x0c, 0xd8, 0x3c, 0x22, 0xfc, 0x4c, 0x40, 0xdf,\n\t0xe0, 0x04, 0x0f, 0x19, 0x7a, 0x08, 0xa6, 0x8a, 0x3e, 0x0a, 0xeb, 0x46, 0xcb, 0x68, 0x3b, 0x7e,\n\t0x55, 0x7e, 0x9f, 0x84, 0xe8, 0x05, 0x00, 0xe3, 0x38, 0xe1, 0x81, 0x70, 0x58, 0xbf, 0xd5, 0x32,\n\t0xda, 0xf6, 0x7e, 0xc3, 0x53, 0xd1, 0x78, 0x69, 0x34, 0xde, 0x59, 0x1a, 0xcd, 0x81, 0x79, 0xf1,\n\t0x57, 0xb3, 0xf4, 0xeb, 0xdf, 0x4d, 0xc3, 0xb7, 0xa4, 0x9d, 0xf8, 0x83, 0x3e, 0x07, 0x93, 0xd0,\n\t0x50, 0x49, 0x94, 0xd7, 0x90, 0xa8, 0x12, 0x1a, 0x0a, 0xdc, 0x3d, 0x85, 0xbb, 0x69, 0xc8, 0xcc,\n\t0x27, 0xef, 0xc6, 0x84, 0x71, 0xf4, 0x31, 0x54, 0xde, 0x8d, 0x49, 0x32, 0xab, 0x1b, 0xad, 0x72,\n\t0xdb, 0xde, 0xdf, 0xf1, 0xae, 0xd4, 0xda, 0x5b, 0x4c, 0xd3, 0x57, 0x7c, 0xb7, 0x06, 0xe8, 0x88,\n\t0xf0, 0xef, 0x48, 0x32, 0x89, 0xe6, 0x72, 0xee, 0x1e, 0xdc, 0x5f, 0x40, 0xd9, 0x28, 0xa6, 0x8c,\n\t0xa0, 0x06, 0x98, 0x4c, 0x63, 0xd2, 0x91, 0xe5, 0x67, 0xdf, 0xee, 0x4b, 0xa8, 0x1d, 0x11, 0xfe,\n\t0x6a, 0x44, 0xd4, 0x05, 0x66, 0x91, 0xd5, 0xa1, 0xaa, 0x39, 0xb2, 0x9a, 0x96, 0x9f, 0x7e, 0xa2,\n\t0x2d, 0xb0, 0xd8, 0x08, 0xd3, 0xe0, 0x3c, 0xa2, 0xa1, 0x2c, 0xa6, 0x90, 0x1b, 0x61, 0x7a, 0x1a,\n\t0xd1, 0xd0, 0x7d, 0x06, 0x56, 0xa6, 0x85, 0x10, 0xdc, 0xa6, 0x78, 0x98, 0x0a, 0xc8, 0xf3, 0x6a,\n\t0xeb, 0xef, 0xe1, 0xc1, 0x52, 0x30, 0x3a, 0x83, 0x67, 0x00, 0x71, 0x86, 0xea, 0x62, 0xbd, 0x57,\n\t0x50, 0xac, 0xcc, 0xd4, 0xcf, 0xf1, 0xdd, 0x57, 0x60, 0x9e, 0x92, 0xd9, 0x0f, 0x78, 0x30, 0x26,\n\t0xe8, 0x2e, 0x94, 0xcf, 0xc9, 0x4c, 0x87, 0x24, 0x8e, 0x68, 0x0f, 0x2a, 0x13, 0xf1, 0x4b, 0x37,\n\t0xc6, 0x56, 0x81, 0xec, 0x73, 0xaa, 0xac, 0x7d, 0xc5, 0x74, 0x2f, 0x6e, 0x81, 0x99, 0x62, 0xe8,\n\t0x31, 0x38, 0x8c, 0x27, 0x11, 0xed, 0x07, 0x4a, 0x46, 0x4a, 0x1f, 0x97, 0x7c, 0x5b, 0xa1, 0x8a,\n\t0xd4, 0x04, 0xe8, 0xc5, 0xf1, 0x20, 0x98, 0x7b, 0x32, 0x8f, 0x4b, 0xbe, 0x25, 0x30, 0x45, 0x78,\n\t0x04, 0x56, 0x44, 0xb9, 0xfe, 0x2f, 0xfa, 0xab, 0x7c, 0x5c, 0xf2, 0xcd, 0x88, 0xf2, 0xcc, 0x49,\n\t0x18, 0x8f, 0x7b, 0x03, 0xa2, 0x19, 0xb7, 0x5b, 0x46, 0xdb, 0x10, 0x4e, 0x14, 0xaa, 0x48, 0x5f,\n\t0x80, 0x8d, 0x93, 0x04, 0xcf, 0x34, 0xa7, 0x22, 0xf3, 0x79, 0x54, 0x94, 0x8f, 0x60, 0x49, 0x9b,\n\t0xe3, 0x92, 0x0f, 0x38, 0xfb, 0x42, 0x5d, 0x70, 0xce, 0x27, 0x83, 0x88, 0xa5, 0x81, 0x6c, 0x48,\n\t0x89, 0x66, 0x81, 0x44, 0x5a, 0xd0, 0xaf, 0x22, 0xc6, 0x45, 0x1c, 0xca, 0x4c, 0xa9, 0xec, 0x80,\n\t0xdd, 0x9b, 0x71, 0xc2, 0xb4, 0x48, 0x55, 0xbc, 0x46, 0xe1, 0x48, 0x82, 0x92, 0x72, 0x50, 0xd5,\n\t0x45, 0x77, 0x5f, 0x80, 0x93, 0x97, 0x42, 0x4f, 0x61, 0x43, 0xfe, 0x48, 0x6f, 0x79, 0x6b, 0x85,\n\t0x6f, 0x5f, 0x53, 0xdd, 0xe7, 0x00, 0xf3, 0x94, 0x6e, 0x24, 0x91, 0xdd, 0x68, 0x2a, 0xf1, 0x6f,\n\t0x19, 0x6a, 0xf2, 0x9d, 0x7d, 0x2b, 0xde, 0x97, 0x7c, 0x6c, 0x84, 0x93, 0x84, 0xa1, 0x1d, 0x70,\n\t0x74, 0xe7, 0x07, 0xb9, 0x66, 0xb6, 0x35, 0xf6, 0xb5, 0xe8, 0xe9, 0x27, 0xb0, 0x99, 0x75, 0x9b,\n\t0x22, 0xa9, 0xc6, 0xbe, 0x93, 0xa1, 0x92, 0xf6, 0x29, 0x00, 0xe6, 0x3c, 0x89, 0x7a, 0x63, 0x4e,\n\t0x58, 0xbd, 0x7c, 0x7d, 0x7a, 0x39, 0x3a, 0xfa, 0x12, 0x36, 0xe7, 0x33, 0x2c, 0x18, 0x46, 0x54,\n\t0xb6, 0xc0, 0x4d, 0x87, 0x90, 0x93, 0xcd, 0xb1, 0x97, 0x11, 0x5d, 0xd6, 0xc2, 0x53, 0xdd, 0x2a,\n\t0x6b, 0x6b, 0xe1, 0x29, 0x3a, 0x04, 0x27, 0x1d, 0xfe, 0x32, 0x2a, 0xd5, 0x31, 0x0f, 0xaf, 0x28,\n\t0x75, 0x35, 0x49, 0x09, 0xfd, 0x26, 0x84, 0xec, 0xd4, 0x50, 0xc4, 0xb4, 0xa0, 0x83, 0xa7, 0xb2,\n\t0x69, 0xd6, 0xd6, 0xc1, 0x53, 0x75, 0x5d, 0x38, 0x79, 0xf3, 0x36, 0x08, 0xc9, 0x88, 0xbf, 0xad,\n\t0x9b, 0x2d, 0xa3, 0x5d, 0x11, 0xd7, 0x25, 0xb0, 0xae, 0x80, 0x5c, 0x1f, 0xee, 0x1d, 0x46, 0x34,\n\t0x5c, 0x9c, 0xc4, 0x9f, 0xcd, 0x27, 0xb1, 0x70, 0xbc, 0x5b, 0x70, 0x2f, 0x45, 0xed, 0x91, 0xce,\n\t0xe3, 0xdf, 0x0d, 0x70, 0x0e, 0xe3, 0xb1, 0x56, 0x3d, 0xe9, 0xae, 0x5a, 0x47, 0x9f, 0x40, 0x45,\n\t0x96, 0x70, 0xad, 0x4d, 0xa4, 0x4c, 0xd0, 0x47, 0x50, 0x26, 0x34, 0x5c, 0x6b, 0x01, 0x09, 0x03,\n\t0xf7, 0x0c, 0x6a, 0x59, 0xce, 0x27, 0xdd, 0xfc, 0x60, 0xb5, 0xd2, 0x30, 0xd3, 0xe7, 0x52, 0xf4,\n\t0xda, 0xf3, 0xa9, 0xf9, 0xa6, 0x4e, 0x84, 0xed, 0xff, 0x57, 0x06, 0x5b, 0xa2, 0x3e, 0xc1, 0x21,\n\t0x49, 0xd0, 0x6b, 0xb0, 0xb2, 0x15, 0x87, 0x1e, 0xaf, 0x58, 0x66, 0x69, 0xd9, 0x1b, 0x6d, 0x6f,\n\t0x61, 0xe7, 0xab, 0x4c, 0x3c, 0xb5, 0xea, 0x27, 0x7b, 0xaa, 0xe4, 0xac, 0x8b, 0x39, 0x76, 0x4b,\n\t0x1f, 0x1a, 0xe8, 0x35, 0xd8, 0xb9, 0xfd, 0x86, 0x9e, 0x14, 0x7b, 0x58, 0xda, 0x8a, 0x8d, 0x0f,\n\t0xae, 0xa3, 0xa9, 0x5a, 0xb8, 0x25, 0x14, 0xc2, 0x9d, 0x85, 0xfd, 0x83, 0x76, 0x8b, 0x4d, 0xaf,\n\t0xac, 0xcb, 0x46, 0xfb, 0x7a, 0x62, 0xe6, 0xe5, 0x27, 0x80, 0x79, 0xff, 0xa1, 0xf7, 0x8b, 0xca,\n\t0xbd, 0xdc, 0x9e, 0x6b, 0xd6, 0x29, 0x00, 0x27, 0x7f, 0xdb, 0x37, 0xf4, 0xb1, 0xbb, 0x8a, 0x95,\n\t0x6b, 0x1a, 0xb7, 0x74, 0xf0, 0xe0, 0xe2, 0x72, 0xdb, 0xf8, 0xf3, 0x72, 0xdb, 0xf8, 0xe7, 0x72,\n\t0xdb, 0xf8, 0xb1, 0xaa, 0x0d, 0x7a, 0x1b, 0x32, 0xac, 0xa7, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff,\n\t0xba, 0xc1, 0x9f, 0xcf, 0x48, 0x0a, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// TraceReaderClient is the client API for TraceReader service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype TraceReaderClient interface {\n\t// GetTraces returns a stream that retrieves all traces with given IDs.\n\t//\n\t// Chunking requirements:\n\t// - A single TracesData chunk MUST NOT contain spans from multiple traces.\n\t// - Large traces MAY be split across multiple, *consecutive* TracesData chunks.\n\t// - Each returned TracesData object MUST NOT be empty.\n\t//\n\t// Edge cases:\n\t// - If no spans are found for any given trace ID, the ID is ignored.\n\t// - If none of the trace IDs are found in the storage, an empty response is returned.\n\tGetTraces(ctx context.Context, in *GetTracesRequest, opts ...grpc.CallOption) (TraceReader_GetTracesClient, error)\n\t// GetServices returns all service names known to the backend from traces\n\t// within its retention period.\n\tGetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error)\n\t// GetOperations returns all operation names for a given service\n\t// known to the backend from traces within its retention period.\n\tGetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error)\n\t// FindTraces returns a stream that retrieves traces matching query parameters.\n\t//\n\t// The chunking rules are the same as for GetTraces.\n\t//\n\t// If no matching traces are found, an empty stream is returned.\n\tFindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (TraceReader_FindTracesClient, error)\n\t// FindTraceIDs returns a stream that retrieves IDs of traces matching query parameters.\n\t//\n\t// If no matching traces are found, an empty stream is returned.\n\t//\n\t// This call behaves identically to FindTraces, except that it returns only the list\n\t// of matching trace IDs. This is useful in some contexts, such as batch jobs, where a\n\t// large list of trace IDs may be queried first and then the full traces are loaded\n\t// in batches.\n\tFindTraceIDs(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (*FindTraceIDsResponse, error)\n}\n\ntype traceReaderClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewTraceReaderClient(cc *grpc.ClientConn) TraceReaderClient {\n\treturn &traceReaderClient{cc}\n}\n\nfunc (c *traceReaderClient) GetTraces(ctx context.Context, in *GetTracesRequest, opts ...grpc.CallOption) (TraceReader_GetTracesClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_TraceReader_serviceDesc.Streams[0], \"/jaeger.storage.v2.TraceReader/GetTraces\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &traceReaderGetTracesClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype TraceReader_GetTracesClient interface {\n\tRecv() (*v1.TracesData, error)\n\tgrpc.ClientStream\n}\n\ntype traceReaderGetTracesClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *traceReaderGetTracesClient) Recv() (*v1.TracesData, error) {\n\tm := new(v1.TracesData)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *traceReaderClient) GetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error) {\n\tout := new(GetServicesResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v2.TraceReader/GetServices\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *traceReaderClient) GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error) {\n\tout := new(GetOperationsResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v2.TraceReader/GetOperations\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *traceReaderClient) FindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (TraceReader_FindTracesClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_TraceReader_serviceDesc.Streams[1], \"/jaeger.storage.v2.TraceReader/FindTraces\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &traceReaderFindTracesClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype TraceReader_FindTracesClient interface {\n\tRecv() (*v1.TracesData, error)\n\tgrpc.ClientStream\n}\n\ntype traceReaderFindTracesClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *traceReaderFindTracesClient) Recv() (*v1.TracesData, error) {\n\tm := new(v1.TracesData)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *traceReaderClient) FindTraceIDs(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (*FindTraceIDsResponse, error) {\n\tout := new(FindTraceIDsResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v2.TraceReader/FindTraceIDs\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// TraceReaderServer is the server API for TraceReader service.\ntype TraceReaderServer interface {\n\t// GetTraces returns a stream that retrieves all traces with given IDs.\n\t//\n\t// Chunking requirements:\n\t// - A single TracesData chunk MUST NOT contain spans from multiple traces.\n\t// - Large traces MAY be split across multiple, *consecutive* TracesData chunks.\n\t// - Each returned TracesData object MUST NOT be empty.\n\t//\n\t// Edge cases:\n\t// - If no spans are found for any given trace ID, the ID is ignored.\n\t// - If none of the trace IDs are found in the storage, an empty response is returned.\n\tGetTraces(*GetTracesRequest, TraceReader_GetTracesServer) error\n\t// GetServices returns all service names known to the backend from traces\n\t// within its retention period.\n\tGetServices(context.Context, *GetServicesRequest) (*GetServicesResponse, error)\n\t// GetOperations returns all operation names for a given service\n\t// known to the backend from traces within its retention period.\n\tGetOperations(context.Context, *GetOperationsRequest) (*GetOperationsResponse, error)\n\t// FindTraces returns a stream that retrieves traces matching query parameters.\n\t//\n\t// The chunking rules are the same as for GetTraces.\n\t//\n\t// If no matching traces are found, an empty stream is returned.\n\tFindTraces(*FindTracesRequest, TraceReader_FindTracesServer) error\n\t// FindTraceIDs returns a stream that retrieves IDs of traces matching query parameters.\n\t//\n\t// If no matching traces are found, an empty stream is returned.\n\t//\n\t// This call behaves identically to FindTraces, except that it returns only the list\n\t// of matching trace IDs. This is useful in some contexts, such as batch jobs, where a\n\t// large list of trace IDs may be queried first and then the full traces are loaded\n\t// in batches.\n\tFindTraceIDs(context.Context, *FindTracesRequest) (*FindTraceIDsResponse, error)\n}\n\n// UnimplementedTraceReaderServer can be embedded to have forward compatible implementations.\ntype UnimplementedTraceReaderServer struct {\n}\n\nfunc (*UnimplementedTraceReaderServer) GetTraces(req *GetTracesRequest, srv TraceReader_GetTracesServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method GetTraces not implemented\")\n}\nfunc (*UnimplementedTraceReaderServer) GetServices(ctx context.Context, req *GetServicesRequest) (*GetServicesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetServices not implemented\")\n}\nfunc (*UnimplementedTraceReaderServer) GetOperations(ctx context.Context, req *GetOperationsRequest) (*GetOperationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetOperations not implemented\")\n}\nfunc (*UnimplementedTraceReaderServer) FindTraces(req *FindTracesRequest, srv TraceReader_FindTracesServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method FindTraces not implemented\")\n}\nfunc (*UnimplementedTraceReaderServer) FindTraceIDs(ctx context.Context, req *FindTracesRequest) (*FindTraceIDsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method FindTraceIDs not implemented\")\n}\n\nfunc RegisterTraceReaderServer(s *grpc.Server, srv TraceReaderServer) {\n\ts.RegisterService(&_TraceReader_serviceDesc, srv)\n}\n\nfunc _TraceReader_GetTraces_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(GetTracesRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(TraceReaderServer).GetTraces(m, &traceReaderGetTracesServer{stream})\n}\n\ntype TraceReader_GetTracesServer interface {\n\tSend(*v1.TracesData) error\n\tgrpc.ServerStream\n}\n\ntype traceReaderGetTracesServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *traceReaderGetTracesServer) Send(m *v1.TracesData) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _TraceReader_GetServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetServicesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TraceReaderServer).GetServices(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v2.TraceReader/GetServices\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TraceReaderServer).GetServices(ctx, req.(*GetServicesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _TraceReader_GetOperations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetOperationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TraceReaderServer).GetOperations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v2.TraceReader/GetOperations\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TraceReaderServer).GetOperations(ctx, req.(*GetOperationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _TraceReader_FindTraces_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(FindTracesRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(TraceReaderServer).FindTraces(m, &traceReaderFindTracesServer{stream})\n}\n\ntype TraceReader_FindTracesServer interface {\n\tSend(*v1.TracesData) error\n\tgrpc.ServerStream\n}\n\ntype traceReaderFindTracesServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *traceReaderFindTracesServer) Send(m *v1.TracesData) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _TraceReader_FindTraceIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(FindTracesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TraceReaderServer).FindTraceIDs(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v2.TraceReader/FindTraceIDs\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TraceReaderServer).FindTraceIDs(ctx, req.(*FindTracesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _TraceReader_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v2.TraceReader\",\n\tHandlerType: (*TraceReaderServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetServices\",\n\t\t\tHandler:    _TraceReader_GetServices_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetOperations\",\n\t\t\tHandler:    _TraceReader_GetOperations_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"FindTraceIDs\",\n\t\t\tHandler:    _TraceReader_FindTraceIDs_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"GetTraces\",\n\t\t\tHandler:       _TraceReader_GetTraces_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"FindTraces\",\n\t\t\tHandler:       _TraceReader_FindTraces_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"trace_storage.proto\",\n}\n\nfunc (m *GetTraceParams) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetTraceParams) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetTraceParams) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tn1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):])\n\tif err1 != nil {\n\t\treturn 0, err1\n\t}\n\ti -= n1\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n1))\n\ti--\n\tdAtA[i] = 0x1a\n\tn2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):])\n\tif err2 != nil {\n\t\treturn 0, err2\n\t}\n\ti -= n2\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n2))\n\ti--\n\tdAtA[i] = 0x12\n\tif len(m.TraceId) > 0 {\n\t\ti -= len(m.TraceId)\n\t\tcopy(dAtA[i:], m.TraceId)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.TraceId)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetTracesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetTracesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetTracesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Query) > 0 {\n\t\tfor iNdEx := len(m.Query) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Query[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetServicesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetServicesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetServicesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetServicesResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetServicesResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetServicesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Services) > 0 {\n\t\tfor iNdEx := len(m.Services) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\ti -= len(m.Services[iNdEx])\n\t\t\tcopy(dAtA[i:], m.Services[iNdEx])\n\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.Services[iNdEx])))\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetOperationsRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetOperationsRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetOperationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.SpanKind) > 0 {\n\t\ti -= len(m.SpanKind)\n\t\tcopy(dAtA[i:], m.SpanKind)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.SpanKind)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Service) > 0 {\n\t\ti -= len(m.Service)\n\t\tcopy(dAtA[i:], m.Service)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.Service)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Operation) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Operation) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Operation) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.SpanKind) > 0 {\n\t\ti -= len(m.SpanKind)\n\t\tcopy(dAtA[i:], m.SpanKind)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.SpanKind)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Name) > 0 {\n\t\ti -= len(m.Name)\n\t\tcopy(dAtA[i:], m.Name)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.Name)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetOperationsResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetOperationsResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetOperationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Operations) > 0 {\n\t\tfor iNdEx := len(m.Operations) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Operations[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *KeyValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *KeyValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *KeyValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Value != nil {\n\t\t{\n\t\t\tsize, err := m.Value.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Key) > 0 {\n\t\ti -= len(m.Key)\n\t\tcopy(dAtA[i:], m.Key)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.Key)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *AnyValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *AnyValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Value != nil {\n\t\t{\n\t\t\tsize := m.Value.Size()\n\t\t\ti -= size\n\t\t\tif _, err := m.Value.MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *AnyValue_StringValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_StringValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= len(m.StringValue)\n\tcopy(dAtA[i:], m.StringValue)\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.StringValue)))\n\ti--\n\tdAtA[i] = 0xa\n\treturn len(dAtA) - i, nil\n}\nfunc (m *AnyValue_BoolValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_BoolValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti--\n\tif m.BoolValue {\n\t\tdAtA[i] = 1\n\t} else {\n\t\tdAtA[i] = 0\n\t}\n\ti--\n\tdAtA[i] = 0x10\n\treturn len(dAtA) - i, nil\n}\nfunc (m *AnyValue_IntValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_IntValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(m.IntValue))\n\ti--\n\tdAtA[i] = 0x18\n\treturn len(dAtA) - i, nil\n}\nfunc (m *AnyValue_DoubleValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\ti -= 8\n\tencoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DoubleValue))))\n\ti--\n\tdAtA[i] = 0x21\n\treturn len(dAtA) - i, nil\n}\nfunc (m *AnyValue_ArrayValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_ArrayValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.ArrayValue != nil {\n\t\t{\n\t\t\tsize, err := m.ArrayValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x2a\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *AnyValue_KvlistValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_KvlistValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.KvlistValue != nil {\n\t\t{\n\t\t\tsize, err := m.KvlistValue.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x32\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *AnyValue_BytesValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *AnyValue_BytesValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\tif m.BytesValue != nil {\n\t\ti -= len(m.BytesValue)\n\t\tcopy(dAtA[i:], m.BytesValue)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.BytesValue)))\n\t\ti--\n\t\tdAtA[i] = 0x3a\n\t}\n\treturn len(dAtA) - i, nil\n}\nfunc (m *KeyValueList) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *KeyValueList) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *KeyValueList) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Values) > 0 {\n\t\tfor iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Values[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *ArrayValue) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *ArrayValue) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *ArrayValue) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Values) > 0 {\n\t\tfor iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Values[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *TraceQueryParameters) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *TraceQueryParameters) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *TraceQueryParameters) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.SearchDepth != 0 {\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(m.SearchDepth))\n\t\ti--\n\t\tdAtA[i] = 0x40\n\t}\n\tn6, err6 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.DurationMax, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMax):])\n\tif err6 != nil {\n\t\treturn 0, err6\n\t}\n\ti -= n6\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n6))\n\ti--\n\tdAtA[i] = 0x3a\n\tn7, err7 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.DurationMin, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMin):])\n\tif err7 != nil {\n\t\treturn 0, err7\n\t}\n\ti -= n7\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n7))\n\ti--\n\tdAtA[i] = 0x32\n\tn8, err8 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTimeMax, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMax):])\n\tif err8 != nil {\n\t\treturn 0, err8\n\t}\n\ti -= n8\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n8))\n\ti--\n\tdAtA[i] = 0x2a\n\tn9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTimeMin, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMin):])\n\tif err9 != nil {\n\t\treturn 0, err9\n\t}\n\ti -= n9\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n9))\n\ti--\n\tdAtA[i] = 0x22\n\tif len(m.Attributes) > 0 {\n\t\tfor iNdEx := len(m.Attributes) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Attributes[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x1a\n\t\t}\n\t}\n\tif len(m.OperationName) > 0 {\n\t\ti -= len(m.OperationName)\n\t\tcopy(dAtA[i:], m.OperationName)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.OperationName)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.ServiceName) > 0 {\n\t\ti -= len(m.ServiceName)\n\t\tcopy(dAtA[i:], m.ServiceName)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.ServiceName)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FindTracesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FindTracesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FindTracesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Query != nil {\n\t\t{\n\t\t\tsize, err := m.Query.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FoundTraceID) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FoundTraceID) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FoundTraceID) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tn11, err11 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.End, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.End):])\n\tif err11 != nil {\n\t\treturn 0, err11\n\t}\n\ti -= n11\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n11))\n\ti--\n\tdAtA[i] = 0x1a\n\tn12, err12 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Start, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Start):])\n\tif err12 != nil {\n\t\treturn 0, err12\n\t}\n\ti -= n12\n\ti = encodeVarintTraceStorage(dAtA, i, uint64(n12))\n\ti--\n\tdAtA[i] = 0x12\n\tif len(m.TraceId) > 0 {\n\t\ti -= len(m.TraceId)\n\t\tcopy(dAtA[i:], m.TraceId)\n\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(len(m.TraceId)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FindTraceIDsResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FindTraceIDsResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FindTraceIDsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.TraceIds) > 0 {\n\t\tfor iNdEx := len(m.TraceIds) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.TraceIds[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintTraceStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc encodeVarintTraceStorage(dAtA []byte, offset int, v uint64) int {\n\toffset -= sovTraceStorage(v)\n\tbase := offset\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn base\n}\nfunc (m *GetTraceParams) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.TraceId)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetTracesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Query) > 0 {\n\t\tfor _, e := range m.Query {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetServicesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetServicesResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Services) > 0 {\n\t\tfor _, s := range m.Services {\n\t\t\tl = len(s)\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetOperationsRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Service)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tl = len(m.SpanKind)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Operation) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Name)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tl = len(m.SpanKind)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetOperationsResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Operations) > 0 {\n\t\tfor _, e := range m.Operations {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *KeyValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Key)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tif m.Value != nil {\n\t\tl = m.Value.Size()\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *AnyValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Value != nil {\n\t\tn += m.Value.Size()\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *AnyValue_StringValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.StringValue)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\treturn n\n}\nfunc (m *AnyValue_BoolValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 2\n\treturn n\n}\nfunc (m *AnyValue_IntValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 1 + sovTraceStorage(uint64(m.IntValue))\n\treturn n\n}\nfunc (m *AnyValue_DoubleValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tn += 9\n\treturn n\n}\nfunc (m *AnyValue_ArrayValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.ArrayValue != nil {\n\t\tl = m.ArrayValue.Size()\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *AnyValue_KvlistValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.KvlistValue != nil {\n\t\tl = m.KvlistValue.Size()\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *AnyValue_BytesValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.BytesValue != nil {\n\t\tl = len(m.BytesValue)\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\treturn n\n}\nfunc (m *KeyValueList) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Values) > 0 {\n\t\tfor _, e := range m.Values {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *ArrayValue) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Values) > 0 {\n\t\tfor _, e := range m.Values {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *TraceQueryParameters) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.ServiceName)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tl = len(m.OperationName)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tif len(m.Attributes) > 0 {\n\t\tfor _, e := range m.Attributes {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMin)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMax)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMin)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMax)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tif m.SearchDepth != 0 {\n\t\tn += 1 + sovTraceStorage(uint64(m.SearchDepth))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FindTracesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Query != nil {\n\t\tl = m.Query.Size()\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FoundTraceID) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.TraceId)\n\tif l > 0 {\n\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t}\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.Start)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.End)\n\tn += 1 + l + sovTraceStorage(uint64(l))\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FindTraceIDsResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.TraceIds) > 0 {\n\t\tfor _, e := range m.TraceIds {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovTraceStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc sovTraceStorage(x uint64) (n int) {\n\treturn (math_bits.Len64(x|1) + 6) / 7\n}\nfunc sozTraceStorage(x uint64) (n int) {\n\treturn sovTraceStorage(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *GetTraceParams) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetTraceParams: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetTraceParams: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field TraceId\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.TraceId = append(m.TraceId[:0], dAtA[iNdEx:postIndex]...)\n\t\t\tif m.TraceId == nil {\n\t\t\t\tm.TraceId = []byte{}\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field EndTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetTracesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetTracesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetTracesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Query\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Query = append(m.Query, &GetTraceParams{})\n\t\t\tif err := m.Query[len(m.Query)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetServicesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetServicesResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Services\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Services = append(m.Services, string(dAtA[iNdEx:postIndex]))\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetOperationsRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Service\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Service = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SpanKind\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.SpanKind = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Operation) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Operation: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Operation: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Name\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Name = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SpanKind\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.SpanKind = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetOperationsResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Operations\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Operations = append(m.Operations, &Operation{})\n\t\t\tif err := m.Operations[len(m.Operations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *KeyValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: KeyValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: KeyValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Key\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Key = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Value\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Value == nil {\n\t\t\t\tm.Value = &AnyValue{}\n\t\t\t}\n\t\t\tif err := m.Value.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *AnyValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: AnyValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: AnyValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StringValue\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Value = &AnyValue_StringValue{string(dAtA[iNdEx:postIndex])}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field BoolValue\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tb := bool(v != 0)\n\t\t\tm.Value = &AnyValue_BoolValue{b}\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field IntValue\", wireType)\n\t\t\t}\n\t\t\tvar v int64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Value = &AnyValue_IntValue{v}\n\t\tcase 4:\n\t\t\tif wireType != 1 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DoubleValue\", wireType)\n\t\t\t}\n\t\t\tvar v uint64\n\t\t\tif (iNdEx + 8) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))\n\t\t\tiNdEx += 8\n\t\t\tm.Value = &AnyValue_DoubleValue{float64(math.Float64frombits(v))}\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ArrayValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &ArrayValue{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &AnyValue_ArrayValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field KvlistValue\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := &KeyValueList{}\n\t\t\tif err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tm.Value = &AnyValue_KvlistValue{v}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field BytesValue\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tv := make([]byte, postIndex-iNdEx)\n\t\t\tcopy(v, dAtA[iNdEx:postIndex])\n\t\t\tm.Value = &AnyValue_BytesValue{v}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *KeyValueList) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: KeyValueList: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: KeyValueList: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Values\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Values = append(m.Values, &KeyValue{})\n\t\t\tif err := m.Values[len(m.Values)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *ArrayValue) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: ArrayValue: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: ArrayValue: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Values\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Values = append(m.Values, &AnyValue{})\n\t\t\tif err := m.Values[len(m.Values)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *TraceQueryParameters) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: TraceQueryParameters: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: TraceQueryParameters: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ServiceName\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.ServiceName = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OperationName\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.OperationName = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Attributes\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Attributes = append(m.Attributes, &KeyValue{})\n\t\t\tif err := m.Attributes[len(m.Attributes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTimeMin\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTimeMin, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTimeMax\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTimeMax, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DurationMin\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.DurationMin, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DurationMax\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.DurationMax, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 8:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SearchDepth\", wireType)\n\t\t\t}\n\t\t\tm.SearchDepth = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.SearchDepth |= int32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FindTracesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FindTracesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FindTracesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Query\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Query == nil {\n\t\t\t\tm.Query = &TraceQueryParameters{}\n\t\t\t}\n\t\t\tif err := m.Query.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FoundTraceID) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FoundTraceID: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FoundTraceID: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field TraceId\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.TraceId = append(m.TraceId[:0], dAtA[iNdEx:postIndex]...)\n\t\t\tif m.TraceId == nil {\n\t\t\t\tm.TraceId = []byte{}\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Start\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Start, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field End\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.End, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FindTraceIDsResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FindTraceIDsResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FindTraceIDsResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field TraceIds\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.TraceIds = append(m.TraceIds, &FoundTraceID{})\n\t\t\tif err := m.TraceIds[len(m.TraceIds)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipTraceStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipTraceStorage(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tdepth := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowTraceStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowTraceStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthTraceStorage\n\t\t\t}\n\t\t\tiNdEx += length\n\t\tcase 3:\n\t\t\tdepth++\n\t\tcase 4:\n\t\t\tif depth == 0 {\n\t\t\t\treturn 0, ErrUnexpectedEndOfGroupTraceStorage\n\t\t\t}\n\t\t\tdepth--\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t\tif iNdEx < 0 {\n\t\t\treturn 0, ErrInvalidLengthTraceStorage\n\t\t}\n\t\tif depth == 0 {\n\t\t\treturn iNdEx, nil\n\t\t}\n\t}\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nvar (\n\tErrInvalidLengthTraceStorage        = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowTraceStorage          = fmt.Errorf(\"proto: integer overflow\")\n\tErrUnexpectedEndOfGroupTraceStorage = fmt.Errorf(\"proto: unexpected end of group\")\n)\n"
  },
  {
    "path": "internal/proto-gen/storage_v1/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage_v1\"\n\tmock \"github.com/stretchr/testify/mock\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// NewSpanWriterPluginClient creates a new instance of SpanWriterPluginClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanWriterPluginClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanWriterPluginClient {\n\tmock := &SpanWriterPluginClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanWriterPluginClient is an autogenerated mock type for the SpanWriterPluginClient type\ntype SpanWriterPluginClient struct {\n\tmock.Mock\n}\n\ntype SpanWriterPluginClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanWriterPluginClient) EXPECT() *SpanWriterPluginClient_Expecter {\n\treturn &SpanWriterPluginClient_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type SpanWriterPluginClient\nfunc (_mock *SpanWriterPluginClient) Close(ctx context.Context, in *storage_v1.CloseWriterRequest, opts ...grpc.CallOption) (*storage_v1.CloseWriterResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Close\")\n\t}\n\n\tvar r0 *storage_v1.CloseWriterResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CloseWriterRequest, ...grpc.CallOption) (*storage_v1.CloseWriterResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CloseWriterRequest, ...grpc.CallOption) *storage_v1.CloseWriterResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.CloseWriterResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.CloseWriterRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanWriterPluginClient_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype SpanWriterPluginClient_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.CloseWriterRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanWriterPluginClient_Expecter) Close(ctx interface{}, in interface{}, opts ...interface{}) *SpanWriterPluginClient_Close_Call {\n\treturn &SpanWriterPluginClient_Close_Call{Call: _e.mock.On(\"Close\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanWriterPluginClient_Close_Call) Run(run func(ctx context.Context, in *storage_v1.CloseWriterRequest, opts ...grpc.CallOption)) *SpanWriterPluginClient_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.CloseWriterRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.CloseWriterRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginClient_Close_Call) Return(closeWriterResponse *storage_v1.CloseWriterResponse, err error) *SpanWriterPluginClient_Close_Call {\n\t_c.Call.Return(closeWriterResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginClient_Close_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.CloseWriterRequest, opts ...grpc.CallOption) (*storage_v1.CloseWriterResponse, error)) *SpanWriterPluginClient_Close_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// WriteSpan provides a mock function for the type SpanWriterPluginClient\nfunc (_mock *SpanWriterPluginClient) WriteSpan(ctx context.Context, in *storage_v1.WriteSpanRequest, opts ...grpc.CallOption) (*storage_v1.WriteSpanResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteSpan\")\n\t}\n\n\tvar r0 *storage_v1.WriteSpanResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest, ...grpc.CallOption) (*storage_v1.WriteSpanResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest, ...grpc.CallOption) *storage_v1.WriteSpanResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.WriteSpanResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.WriteSpanRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanWriterPluginClient_WriteSpan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteSpan'\ntype SpanWriterPluginClient_WriteSpan_Call struct {\n\t*mock.Call\n}\n\n// WriteSpan is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.WriteSpanRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanWriterPluginClient_Expecter) WriteSpan(ctx interface{}, in interface{}, opts ...interface{}) *SpanWriterPluginClient_WriteSpan_Call {\n\treturn &SpanWriterPluginClient_WriteSpan_Call{Call: _e.mock.On(\"WriteSpan\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanWriterPluginClient_WriteSpan_Call) Run(run func(ctx context.Context, in *storage_v1.WriteSpanRequest, opts ...grpc.CallOption)) *SpanWriterPluginClient_WriteSpan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.WriteSpanRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.WriteSpanRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginClient_WriteSpan_Call) Return(writeSpanResponse *storage_v1.WriteSpanResponse, err error) *SpanWriterPluginClient_WriteSpan_Call {\n\t_c.Call.Return(writeSpanResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginClient_WriteSpan_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.WriteSpanRequest, opts ...grpc.CallOption) (*storage_v1.WriteSpanResponse, error)) *SpanWriterPluginClient_WriteSpan_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSpanWriterPluginServer creates a new instance of SpanWriterPluginServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanWriterPluginServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanWriterPluginServer {\n\tmock := &SpanWriterPluginServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanWriterPluginServer is an autogenerated mock type for the SpanWriterPluginServer type\ntype SpanWriterPluginServer struct {\n\tmock.Mock\n}\n\ntype SpanWriterPluginServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanWriterPluginServer) EXPECT() *SpanWriterPluginServer_Expecter {\n\treturn &SpanWriterPluginServer_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type SpanWriterPluginServer\nfunc (_mock *SpanWriterPluginServer) Close(context1 context.Context, closeWriterRequest *storage_v1.CloseWriterRequest) (*storage_v1.CloseWriterResponse, error) {\n\tret := _mock.Called(context1, closeWriterRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Close\")\n\t}\n\n\tvar r0 *storage_v1.CloseWriterResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CloseWriterRequest) (*storage_v1.CloseWriterResponse, error)); ok {\n\t\treturn returnFunc(context1, closeWriterRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CloseWriterRequest) *storage_v1.CloseWriterResponse); ok {\n\t\tr0 = returnFunc(context1, closeWriterRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.CloseWriterResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.CloseWriterRequest) error); ok {\n\t\tr1 = returnFunc(context1, closeWriterRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanWriterPluginServer_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype SpanWriterPluginServer_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\n//   - context1 context.Context\n//   - closeWriterRequest *storage_v1.CloseWriterRequest\nfunc (_e *SpanWriterPluginServer_Expecter) Close(context1 interface{}, closeWriterRequest interface{}) *SpanWriterPluginServer_Close_Call {\n\treturn &SpanWriterPluginServer_Close_Call{Call: _e.mock.On(\"Close\", context1, closeWriterRequest)}\n}\n\nfunc (_c *SpanWriterPluginServer_Close_Call) Run(run func(context1 context.Context, closeWriterRequest *storage_v1.CloseWriterRequest)) *SpanWriterPluginServer_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.CloseWriterRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.CloseWriterRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginServer_Close_Call) Return(closeWriterResponse *storage_v1.CloseWriterResponse, err error) *SpanWriterPluginServer_Close_Call {\n\t_c.Call.Return(closeWriterResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginServer_Close_Call) RunAndReturn(run func(context1 context.Context, closeWriterRequest *storage_v1.CloseWriterRequest) (*storage_v1.CloseWriterResponse, error)) *SpanWriterPluginServer_Close_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// WriteSpan provides a mock function for the type SpanWriterPluginServer\nfunc (_mock *SpanWriterPluginServer) WriteSpan(context1 context.Context, writeSpanRequest *storage_v1.WriteSpanRequest) (*storage_v1.WriteSpanResponse, error) {\n\tret := _mock.Called(context1, writeSpanRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteSpan\")\n\t}\n\n\tvar r0 *storage_v1.WriteSpanResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest) (*storage_v1.WriteSpanResponse, error)); ok {\n\t\treturn returnFunc(context1, writeSpanRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest) *storage_v1.WriteSpanResponse); ok {\n\t\tr0 = returnFunc(context1, writeSpanRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.WriteSpanResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.WriteSpanRequest) error); ok {\n\t\tr1 = returnFunc(context1, writeSpanRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanWriterPluginServer_WriteSpan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteSpan'\ntype SpanWriterPluginServer_WriteSpan_Call struct {\n\t*mock.Call\n}\n\n// WriteSpan is a helper method to define mock.On call\n//   - context1 context.Context\n//   - writeSpanRequest *storage_v1.WriteSpanRequest\nfunc (_e *SpanWriterPluginServer_Expecter) WriteSpan(context1 interface{}, writeSpanRequest interface{}) *SpanWriterPluginServer_WriteSpan_Call {\n\treturn &SpanWriterPluginServer_WriteSpan_Call{Call: _e.mock.On(\"WriteSpan\", context1, writeSpanRequest)}\n}\n\nfunc (_c *SpanWriterPluginServer_WriteSpan_Call) Run(run func(context1 context.Context, writeSpanRequest *storage_v1.WriteSpanRequest)) *SpanWriterPluginServer_WriteSpan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.WriteSpanRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.WriteSpanRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginServer_WriteSpan_Call) Return(writeSpanResponse *storage_v1.WriteSpanResponse, err error) *SpanWriterPluginServer_WriteSpan_Call {\n\t_c.Call.Return(writeSpanResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanWriterPluginServer_WriteSpan_Call) RunAndReturn(run func(context1 context.Context, writeSpanRequest *storage_v1.WriteSpanRequest) (*storage_v1.WriteSpanResponse, error)) *SpanWriterPluginServer_WriteSpan_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewStreamingSpanWriterPluginClient creates a new instance of StreamingSpanWriterPluginClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewStreamingSpanWriterPluginClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *StreamingSpanWriterPluginClient {\n\tmock := &StreamingSpanWriterPluginClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// StreamingSpanWriterPluginClient is an autogenerated mock type for the StreamingSpanWriterPluginClient type\ntype StreamingSpanWriterPluginClient struct {\n\tmock.Mock\n}\n\ntype StreamingSpanWriterPluginClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *StreamingSpanWriterPluginClient) EXPECT() *StreamingSpanWriterPluginClient_Expecter {\n\treturn &StreamingSpanWriterPluginClient_Expecter{mock: &_m.Mock}\n}\n\n// WriteSpanStream provides a mock function for the type StreamingSpanWriterPluginClient\nfunc (_mock *StreamingSpanWriterPluginClient) WriteSpanStream(ctx context.Context, opts ...grpc.CallOption) (storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteSpanStream\")\n\t}\n\n\tvar r0 storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) (storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient, error)); ok {\n\t\treturn returnFunc(ctx, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, ...grpc.CallOption) storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient); ok {\n\t\tr0 = returnFunc(ctx, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// StreamingSpanWriterPluginClient_WriteSpanStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteSpanStream'\ntype StreamingSpanWriterPluginClient_WriteSpanStream_Call struct {\n\t*mock.Call\n}\n\n// WriteSpanStream is a helper method to define mock.On call\n//   - ctx context.Context\n//   - opts ...grpc.CallOption\nfunc (_e *StreamingSpanWriterPluginClient_Expecter) WriteSpanStream(ctx interface{}, opts ...interface{}) *StreamingSpanWriterPluginClient_WriteSpanStream_Call {\n\treturn &StreamingSpanWriterPluginClient_WriteSpanStream_Call{Call: _e.mock.On(\"WriteSpanStream\",\n\t\tappend([]interface{}{ctx}, opts...)...)}\n}\n\nfunc (_c *StreamingSpanWriterPluginClient_WriteSpanStream_Call) Run(run func(ctx context.Context, opts ...grpc.CallOption)) *StreamingSpanWriterPluginClient_WriteSpanStream_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 1 {\n\t\t\tvariadicArgs = args[1].([]grpc.CallOption)\n\t\t}\n\t\targ1 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPluginClient_WriteSpanStream_Call) Return(streamingSpanWriterPlugin_WriteSpanStreamClient storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient, err error) *StreamingSpanWriterPluginClient_WriteSpanStream_Call {\n\t_c.Call.Return(streamingSpanWriterPlugin_WriteSpanStreamClient, err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPluginClient_WriteSpanStream_Call) RunAndReturn(run func(ctx context.Context, opts ...grpc.CallOption) (storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamClient, error)) *StreamingSpanWriterPluginClient_WriteSpanStream_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewStreamingSpanWriterPlugin_WriteSpanStreamClient creates a new instance of StreamingSpanWriterPlugin_WriteSpanStreamClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewStreamingSpanWriterPlugin_WriteSpanStreamClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *StreamingSpanWriterPlugin_WriteSpanStreamClient {\n\tmock := &StreamingSpanWriterPlugin_WriteSpanStreamClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient is an autogenerated mock type for the StreamingSpanWriterPlugin_WriteSpanStreamClient type\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient struct {\n\tmock.Mock\n}\n\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *StreamingSpanWriterPlugin_WriteSpanStreamClient) EXPECT() *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter{mock: &_m.Mock}\n}\n\n// CloseAndRecv provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) CloseAndRecv() (*storage_v1.WriteSpanResponse, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CloseAndRecv\")\n\t}\n\n\tvar r0 *storage_v1.WriteSpanResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (*storage_v1.WriteSpanResponse, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() *storage_v1.WriteSpanResponse); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.WriteSpanResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseAndRecv'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call struct {\n\t*mock.Call\n}\n\n// CloseAndRecv is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) CloseAndRecv() *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call{Call: _e.mock.On(\"CloseAndRecv\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call) Return(writeSpanResponse *storage_v1.WriteSpanResponse, err error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call {\n\t_c.Call.Return(writeSpanResponse, err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call) RunAndReturn(run func() (*storage_v1.WriteSpanResponse, error)) *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseAndRecv_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CloseSend provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) CloseSend() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CloseSend\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call struct {\n\t*mock.Call\n}\n\n// CloseSend is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) CloseSend() *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call{Call: _e.mock.On(\"CloseSend\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call) RunAndReturn(run func() error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_CloseSend_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Context provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) Context() *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call) Return(context1 context.Context) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call) RunAndReturn(run func() context.Context) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Header provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) Header() (metadata.MD, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Header\")\n\t}\n\n\tvar r0 metadata.MD\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (metadata.MD, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call struct {\n\t*mock.Call\n}\n\n// Header is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) Header() *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call{Call: _e.mock.On(\"Header\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call) Return(mD metadata.MD, err error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call {\n\t_c.Call.Return(mD, err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call) RunAndReturn(run func() (metadata.MD, error)) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Header_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) RecvMsg(m interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call) Run(run func(m any)) *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call) RunAndReturn(run func(m any) error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Send provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) Send(writeSpanRequest *storage_v1.WriteSpanRequest) error {\n\tret := _mock.Called(writeSpanRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Send\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.WriteSpanRequest) error); ok {\n\t\tr0 = returnFunc(writeSpanRequest)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call struct {\n\t*mock.Call\n}\n\n// Send is a helper method to define mock.On call\n//   - writeSpanRequest *storage_v1.WriteSpanRequest\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) Send(writeSpanRequest interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call{Call: _e.mock.On(\"Send\", writeSpanRequest)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call) Run(run func(writeSpanRequest *storage_v1.WriteSpanRequest)) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.WriteSpanRequest\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.WriteSpanRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call) RunAndReturn(run func(writeSpanRequest *storage_v1.WriteSpanRequest) error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Send_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) SendMsg(m interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call) Run(run func(m any)) *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call) RunAndReturn(run func(m any) error) *StreamingSpanWriterPlugin_WriteSpanStreamClient_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Trailer provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamClient\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamClient) Trailer() metadata.MD {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Trailer\")\n\t}\n\n\tvar r0 metadata.MD\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call struct {\n\t*mock.Call\n}\n\n// Trailer is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamClient_Expecter) Trailer() *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call{Call: _e.mock.On(\"Trailer\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call) Return(mD metadata.MD) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call {\n\t_c.Call.Return(mD)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call) RunAndReturn(run func() metadata.MD) *StreamingSpanWriterPlugin_WriteSpanStreamClient_Trailer_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewStreamingSpanWriterPluginServer creates a new instance of StreamingSpanWriterPluginServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewStreamingSpanWriterPluginServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *StreamingSpanWriterPluginServer {\n\tmock := &StreamingSpanWriterPluginServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// StreamingSpanWriterPluginServer is an autogenerated mock type for the StreamingSpanWriterPluginServer type\ntype StreamingSpanWriterPluginServer struct {\n\tmock.Mock\n}\n\ntype StreamingSpanWriterPluginServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *StreamingSpanWriterPluginServer) EXPECT() *StreamingSpanWriterPluginServer_Expecter {\n\treturn &StreamingSpanWriterPluginServer_Expecter{mock: &_m.Mock}\n}\n\n// WriteSpanStream provides a mock function for the type StreamingSpanWriterPluginServer\nfunc (_mock *StreamingSpanWriterPluginServer) WriteSpanStream(streamingSpanWriterPlugin_WriteSpanStreamServer storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer) error {\n\tret := _mock.Called(streamingSpanWriterPlugin_WriteSpanStreamServer)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteSpanStream\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer) error); ok {\n\t\tr0 = returnFunc(streamingSpanWriterPlugin_WriteSpanStreamServer)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPluginServer_WriteSpanStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteSpanStream'\ntype StreamingSpanWriterPluginServer_WriteSpanStream_Call struct {\n\t*mock.Call\n}\n\n// WriteSpanStream is a helper method to define mock.On call\n//   - streamingSpanWriterPlugin_WriteSpanStreamServer storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_e *StreamingSpanWriterPluginServer_Expecter) WriteSpanStream(streamingSpanWriterPlugin_WriteSpanStreamServer interface{}) *StreamingSpanWriterPluginServer_WriteSpanStream_Call {\n\treturn &StreamingSpanWriterPluginServer_WriteSpanStream_Call{Call: _e.mock.On(\"WriteSpanStream\", streamingSpanWriterPlugin_WriteSpanStreamServer)}\n}\n\nfunc (_c *StreamingSpanWriterPluginServer_WriteSpanStream_Call) Run(run func(streamingSpanWriterPlugin_WriteSpanStreamServer storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer)) *StreamingSpanWriterPluginServer_WriteSpanStream_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPluginServer_WriteSpanStream_Call) Return(err error) *StreamingSpanWriterPluginServer_WriteSpanStream_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPluginServer_WriteSpanStream_Call) RunAndReturn(run func(streamingSpanWriterPlugin_WriteSpanStreamServer storage_v1.StreamingSpanWriterPlugin_WriteSpanStreamServer) error) *StreamingSpanWriterPluginServer_WriteSpanStream_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewStreamingSpanWriterPlugin_WriteSpanStreamServer creates a new instance of StreamingSpanWriterPlugin_WriteSpanStreamServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewStreamingSpanWriterPlugin_WriteSpanStreamServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *StreamingSpanWriterPlugin_WriteSpanStreamServer {\n\tmock := &StreamingSpanWriterPlugin_WriteSpanStreamServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer is an autogenerated mock type for the StreamingSpanWriterPlugin_WriteSpanStreamServer type\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer struct {\n\tmock.Mock\n}\n\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *StreamingSpanWriterPlugin_WriteSpanStreamServer) EXPECT() *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter{mock: &_m.Mock}\n}\n\n// Context provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) Context() *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call) Return(context1 context.Context) *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call) RunAndReturn(run func() context.Context) *StreamingSpanWriterPlugin_WriteSpanStreamServer_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Recv provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) Recv() (*storage_v1.WriteSpanRequest, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Recv\")\n\t}\n\n\tvar r0 *storage_v1.WriteSpanRequest\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (*storage_v1.WriteSpanRequest, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() *storage_v1.WriteSpanRequest); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.WriteSpanRequest)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Recv'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call struct {\n\t*mock.Call\n}\n\n// Recv is a helper method to define mock.On call\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) Recv() *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call{Call: _e.mock.On(\"Recv\")}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call) Run(run func()) *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call) Return(writeSpanRequest *storage_v1.WriteSpanRequest, err error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call {\n\t_c.Call.Return(writeSpanRequest, err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call) RunAndReturn(run func() (*storage_v1.WriteSpanRequest, error)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_Recv_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) RecvMsg(m interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call) Run(run func(m any)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call) RunAndReturn(run func(m any) error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendAndClose provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) SendAndClose(writeSpanResponse *storage_v1.WriteSpanResponse) error {\n\tret := _mock.Called(writeSpanResponse)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendAndClose\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.WriteSpanResponse) error); ok {\n\t\tr0 = returnFunc(writeSpanResponse)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendAndClose'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call struct {\n\t*mock.Call\n}\n\n// SendAndClose is a helper method to define mock.On call\n//   - writeSpanResponse *storage_v1.WriteSpanResponse\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) SendAndClose(writeSpanResponse interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call{Call: _e.mock.On(\"SendAndClose\", writeSpanResponse)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call) Run(run func(writeSpanResponse *storage_v1.WriteSpanResponse)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.WriteSpanResponse\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.WriteSpanResponse)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call) RunAndReturn(run func(writeSpanResponse *storage_v1.WriteSpanResponse) error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendAndClose_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendHeader provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) SendHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendHeader'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call struct {\n\t*mock.Call\n}\n\n// SendHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) SendHeader(mD interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call{Call: _e.mock.On(\"SendHeader\", mD)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call) Run(run func(mD metadata.MD)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) SendMsg(m interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call) Run(run func(m any)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call) RunAndReturn(run func(m any) error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetHeader provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) SetHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SetHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetHeader'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call struct {\n\t*mock.Call\n}\n\n// SetHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) SetHeader(mD interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call{Call: _e.mock.On(\"SetHeader\", mD)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call) Run(run func(mD metadata.MD)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call) Return(err error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetTrailer provides a mock function for the type StreamingSpanWriterPlugin_WriteSpanStreamServer\nfunc (_mock *StreamingSpanWriterPlugin_WriteSpanStreamServer) SetTrailer(mD metadata.MD) {\n\t_mock.Called(mD)\n\treturn\n}\n\n// StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetTrailer'\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call struct {\n\t*mock.Call\n}\n\n// SetTrailer is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *StreamingSpanWriterPlugin_WriteSpanStreamServer_Expecter) SetTrailer(mD interface{}) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call {\n\treturn &StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call{Call: _e.mock.On(\"SetTrailer\", mD)}\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call) Run(run func(mD metadata.MD)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call) Return() *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call) RunAndReturn(run func(mD metadata.MD)) *StreamingSpanWriterPlugin_WriteSpanStreamServer_SetTrailer_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// NewSpanReaderPluginClient creates a new instance of SpanReaderPluginClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanReaderPluginClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanReaderPluginClient {\n\tmock := &SpanReaderPluginClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanReaderPluginClient is an autogenerated mock type for the SpanReaderPluginClient type\ntype SpanReaderPluginClient struct {\n\tmock.Mock\n}\n\ntype SpanReaderPluginClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanReaderPluginClient) EXPECT() *SpanReaderPluginClient_Expecter {\n\treturn &SpanReaderPluginClient_Expecter{mock: &_m.Mock}\n}\n\n// FindTraceIDs provides a mock function for the type SpanReaderPluginClient\nfunc (_mock *SpanReaderPluginClient) FindTraceIDs(ctx context.Context, in *storage_v1.FindTraceIDsRequest, opts ...grpc.CallOption) (*storage_v1.FindTraceIDsResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraceIDs\")\n\t}\n\n\tvar r0 *storage_v1.FindTraceIDsResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.FindTraceIDsRequest, ...grpc.CallOption) (*storage_v1.FindTraceIDsResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.FindTraceIDsRequest, ...grpc.CallOption) *storage_v1.FindTraceIDsResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.FindTraceIDsResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.FindTraceIDsRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginClient_FindTraceIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraceIDs'\ntype SpanReaderPluginClient_FindTraceIDs_Call struct {\n\t*mock.Call\n}\n\n// FindTraceIDs is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.FindTraceIDsRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanReaderPluginClient_Expecter) FindTraceIDs(ctx interface{}, in interface{}, opts ...interface{}) *SpanReaderPluginClient_FindTraceIDs_Call {\n\treturn &SpanReaderPluginClient_FindTraceIDs_Call{Call: _e.mock.On(\"FindTraceIDs\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanReaderPluginClient_FindTraceIDs_Call) Run(run func(ctx context.Context, in *storage_v1.FindTraceIDsRequest, opts ...grpc.CallOption)) *SpanReaderPluginClient_FindTraceIDs_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.FindTraceIDsRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.FindTraceIDsRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_FindTraceIDs_Call) Return(findTraceIDsResponse *storage_v1.FindTraceIDsResponse, err error) *SpanReaderPluginClient_FindTraceIDs_Call {\n\t_c.Call.Return(findTraceIDsResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_FindTraceIDs_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.FindTraceIDsRequest, opts ...grpc.CallOption) (*storage_v1.FindTraceIDsResponse, error)) *SpanReaderPluginClient_FindTraceIDs_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// FindTraces provides a mock function for the type SpanReaderPluginClient\nfunc (_mock *SpanReaderPluginClient) FindTraces(ctx context.Context, in *storage_v1.FindTracesRequest, opts ...grpc.CallOption) (storage_v1.SpanReaderPlugin_FindTracesClient, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraces\")\n\t}\n\n\tvar r0 storage_v1.SpanReaderPlugin_FindTracesClient\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.FindTracesRequest, ...grpc.CallOption) (storage_v1.SpanReaderPlugin_FindTracesClient, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.FindTracesRequest, ...grpc.CallOption) storage_v1.SpanReaderPlugin_FindTracesClient); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(storage_v1.SpanReaderPlugin_FindTracesClient)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.FindTracesRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginClient_FindTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraces'\ntype SpanReaderPluginClient_FindTraces_Call struct {\n\t*mock.Call\n}\n\n// FindTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.FindTracesRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanReaderPluginClient_Expecter) FindTraces(ctx interface{}, in interface{}, opts ...interface{}) *SpanReaderPluginClient_FindTraces_Call {\n\treturn &SpanReaderPluginClient_FindTraces_Call{Call: _e.mock.On(\"FindTraces\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanReaderPluginClient_FindTraces_Call) Run(run func(ctx context.Context, in *storage_v1.FindTracesRequest, opts ...grpc.CallOption)) *SpanReaderPluginClient_FindTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.FindTracesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.FindTracesRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_FindTraces_Call) Return(spanReaderPlugin_FindTracesClient storage_v1.SpanReaderPlugin_FindTracesClient, err error) *SpanReaderPluginClient_FindTraces_Call {\n\t_c.Call.Return(spanReaderPlugin_FindTracesClient, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_FindTraces_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.FindTracesRequest, opts ...grpc.CallOption) (storage_v1.SpanReaderPlugin_FindTracesClient, error)) *SpanReaderPluginClient_FindTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperations provides a mock function for the type SpanReaderPluginClient\nfunc (_mock *SpanReaderPluginClient) GetOperations(ctx context.Context, in *storage_v1.GetOperationsRequest, opts ...grpc.CallOption) (*storage_v1.GetOperationsResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperations\")\n\t}\n\n\tvar r0 *storage_v1.GetOperationsResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetOperationsRequest, ...grpc.CallOption) (*storage_v1.GetOperationsResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetOperationsRequest, ...grpc.CallOption) *storage_v1.GetOperationsResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.GetOperationsResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetOperationsRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginClient_GetOperations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperations'\ntype SpanReaderPluginClient_GetOperations_Call struct {\n\t*mock.Call\n}\n\n// GetOperations is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.GetOperationsRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanReaderPluginClient_Expecter) GetOperations(ctx interface{}, in interface{}, opts ...interface{}) *SpanReaderPluginClient_GetOperations_Call {\n\treturn &SpanReaderPluginClient_GetOperations_Call{Call: _e.mock.On(\"GetOperations\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanReaderPluginClient_GetOperations_Call) Run(run func(ctx context.Context, in *storage_v1.GetOperationsRequest, opts ...grpc.CallOption)) *SpanReaderPluginClient_GetOperations_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetOperationsRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetOperationsRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_GetOperations_Call) Return(getOperationsResponse *storage_v1.GetOperationsResponse, err error) *SpanReaderPluginClient_GetOperations_Call {\n\t_c.Call.Return(getOperationsResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_GetOperations_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.GetOperationsRequest, opts ...grpc.CallOption) (*storage_v1.GetOperationsResponse, error)) *SpanReaderPluginClient_GetOperations_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetServices provides a mock function for the type SpanReaderPluginClient\nfunc (_mock *SpanReaderPluginClient) GetServices(ctx context.Context, in *storage_v1.GetServicesRequest, opts ...grpc.CallOption) (*storage_v1.GetServicesResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetServices\")\n\t}\n\n\tvar r0 *storage_v1.GetServicesResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetServicesRequest, ...grpc.CallOption) (*storage_v1.GetServicesResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetServicesRequest, ...grpc.CallOption) *storage_v1.GetServicesResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.GetServicesResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetServicesRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginClient_GetServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServices'\ntype SpanReaderPluginClient_GetServices_Call struct {\n\t*mock.Call\n}\n\n// GetServices is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.GetServicesRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanReaderPluginClient_Expecter) GetServices(ctx interface{}, in interface{}, opts ...interface{}) *SpanReaderPluginClient_GetServices_Call {\n\treturn &SpanReaderPluginClient_GetServices_Call{Call: _e.mock.On(\"GetServices\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanReaderPluginClient_GetServices_Call) Run(run func(ctx context.Context, in *storage_v1.GetServicesRequest, opts ...grpc.CallOption)) *SpanReaderPluginClient_GetServices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetServicesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetServicesRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_GetServices_Call) Return(getServicesResponse *storage_v1.GetServicesResponse, err error) *SpanReaderPluginClient_GetServices_Call {\n\t_c.Call.Return(getServicesResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_GetServices_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.GetServicesRequest, opts ...grpc.CallOption) (*storage_v1.GetServicesResponse, error)) *SpanReaderPluginClient_GetServices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetTrace provides a mock function for the type SpanReaderPluginClient\nfunc (_mock *SpanReaderPluginClient) GetTrace(ctx context.Context, in *storage_v1.GetTraceRequest, opts ...grpc.CallOption) (storage_v1.SpanReaderPlugin_GetTraceClient, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTrace\")\n\t}\n\n\tvar r0 storage_v1.SpanReaderPlugin_GetTraceClient\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetTraceRequest, ...grpc.CallOption) (storage_v1.SpanReaderPlugin_GetTraceClient, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetTraceRequest, ...grpc.CallOption) storage_v1.SpanReaderPlugin_GetTraceClient); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(storage_v1.SpanReaderPlugin_GetTraceClient)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetTraceRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginClient_GetTrace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTrace'\ntype SpanReaderPluginClient_GetTrace_Call struct {\n\t*mock.Call\n}\n\n// GetTrace is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.GetTraceRequest\n//   - opts ...grpc.CallOption\nfunc (_e *SpanReaderPluginClient_Expecter) GetTrace(ctx interface{}, in interface{}, opts ...interface{}) *SpanReaderPluginClient_GetTrace_Call {\n\treturn &SpanReaderPluginClient_GetTrace_Call{Call: _e.mock.On(\"GetTrace\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *SpanReaderPluginClient_GetTrace_Call) Run(run func(ctx context.Context, in *storage_v1.GetTraceRequest, opts ...grpc.CallOption)) *SpanReaderPluginClient_GetTrace_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetTraceRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetTraceRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_GetTrace_Call) Return(spanReaderPlugin_GetTraceClient storage_v1.SpanReaderPlugin_GetTraceClient, err error) *SpanReaderPluginClient_GetTrace_Call {\n\t_c.Call.Return(spanReaderPlugin_GetTraceClient, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginClient_GetTrace_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.GetTraceRequest, opts ...grpc.CallOption) (storage_v1.SpanReaderPlugin_GetTraceClient, error)) *SpanReaderPluginClient_GetTrace_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSpanReaderPlugin_GetTraceClient creates a new instance of SpanReaderPlugin_GetTraceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanReaderPlugin_GetTraceClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanReaderPlugin_GetTraceClient {\n\tmock := &SpanReaderPlugin_GetTraceClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanReaderPlugin_GetTraceClient is an autogenerated mock type for the SpanReaderPlugin_GetTraceClient type\ntype SpanReaderPlugin_GetTraceClient struct {\n\tmock.Mock\n}\n\ntype SpanReaderPlugin_GetTraceClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanReaderPlugin_GetTraceClient) EXPECT() *SpanReaderPlugin_GetTraceClient_Expecter {\n\treturn &SpanReaderPlugin_GetTraceClient_Expecter{mock: &_m.Mock}\n}\n\n// CloseSend provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) CloseSend() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CloseSend\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'\ntype SpanReaderPlugin_GetTraceClient_CloseSend_Call struct {\n\t*mock.Call\n}\n\n// CloseSend is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) CloseSend() *SpanReaderPlugin_GetTraceClient_CloseSend_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_CloseSend_Call{Call: _e.mock.On(\"CloseSend\")}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_CloseSend_Call) Run(run func()) *SpanReaderPlugin_GetTraceClient_CloseSend_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_CloseSend_Call) Return(err error) *SpanReaderPlugin_GetTraceClient_CloseSend_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_CloseSend_Call) RunAndReturn(run func() error) *SpanReaderPlugin_GetTraceClient_CloseSend_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Context provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype SpanReaderPlugin_GetTraceClient_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) Context() *SpanReaderPlugin_GetTraceClient_Context_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Context_Call) Run(run func()) *SpanReaderPlugin_GetTraceClient_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Context_Call) Return(context1 context.Context) *SpanReaderPlugin_GetTraceClient_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Context_Call) RunAndReturn(run func() context.Context) *SpanReaderPlugin_GetTraceClient_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Header provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) Header() (metadata.MD, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Header\")\n\t}\n\n\tvar r0 metadata.MD\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (metadata.MD, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPlugin_GetTraceClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'\ntype SpanReaderPlugin_GetTraceClient_Header_Call struct {\n\t*mock.Call\n}\n\n// Header is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) Header() *SpanReaderPlugin_GetTraceClient_Header_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_Header_Call{Call: _e.mock.On(\"Header\")}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Header_Call) Run(run func()) *SpanReaderPlugin_GetTraceClient_Header_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Header_Call) Return(mD metadata.MD, err error) *SpanReaderPlugin_GetTraceClient_Header_Call {\n\t_c.Call.Return(mD, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Header_Call) RunAndReturn(run func() (metadata.MD, error)) *SpanReaderPlugin_GetTraceClient_Header_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Recv provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) Recv() (*storage_v1.SpansResponseChunk, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Recv\")\n\t}\n\n\tvar r0 *storage_v1.SpansResponseChunk\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (*storage_v1.SpansResponseChunk, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() *storage_v1.SpansResponseChunk); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.SpansResponseChunk)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPlugin_GetTraceClient_Recv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Recv'\ntype SpanReaderPlugin_GetTraceClient_Recv_Call struct {\n\t*mock.Call\n}\n\n// Recv is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) Recv() *SpanReaderPlugin_GetTraceClient_Recv_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_Recv_Call{Call: _e.mock.On(\"Recv\")}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Recv_Call) Run(run func()) *SpanReaderPlugin_GetTraceClient_Recv_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Recv_Call) Return(spansResponseChunk *storage_v1.SpansResponseChunk, err error) *SpanReaderPlugin_GetTraceClient_Recv_Call {\n\t_c.Call.Return(spansResponseChunk, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Recv_Call) RunAndReturn(run func() (*storage_v1.SpansResponseChunk, error)) *SpanReaderPlugin_GetTraceClient_Recv_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype SpanReaderPlugin_GetTraceClient_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) RecvMsg(m interface{}) *SpanReaderPlugin_GetTraceClient_RecvMsg_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_RecvMsg_Call) Run(run func(m any)) *SpanReaderPlugin_GetTraceClient_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_RecvMsg_Call) Return(err error) *SpanReaderPlugin_GetTraceClient_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_RecvMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_GetTraceClient_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype SpanReaderPlugin_GetTraceClient_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) SendMsg(m interface{}) *SpanReaderPlugin_GetTraceClient_SendMsg_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_SendMsg_Call) Run(run func(m any)) *SpanReaderPlugin_GetTraceClient_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_SendMsg_Call) Return(err error) *SpanReaderPlugin_GetTraceClient_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_SendMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_GetTraceClient_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Trailer provides a mock function for the type SpanReaderPlugin_GetTraceClient\nfunc (_mock *SpanReaderPlugin_GetTraceClient) Trailer() metadata.MD {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Trailer\")\n\t}\n\n\tvar r0 metadata.MD\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'\ntype SpanReaderPlugin_GetTraceClient_Trailer_Call struct {\n\t*mock.Call\n}\n\n// Trailer is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_GetTraceClient_Expecter) Trailer() *SpanReaderPlugin_GetTraceClient_Trailer_Call {\n\treturn &SpanReaderPlugin_GetTraceClient_Trailer_Call{Call: _e.mock.On(\"Trailer\")}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Trailer_Call) Run(run func()) *SpanReaderPlugin_GetTraceClient_Trailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Trailer_Call) Return(mD metadata.MD) *SpanReaderPlugin_GetTraceClient_Trailer_Call {\n\t_c.Call.Return(mD)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceClient_Trailer_Call) RunAndReturn(run func() metadata.MD) *SpanReaderPlugin_GetTraceClient_Trailer_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSpanReaderPlugin_FindTracesClient creates a new instance of SpanReaderPlugin_FindTracesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanReaderPlugin_FindTracesClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanReaderPlugin_FindTracesClient {\n\tmock := &SpanReaderPlugin_FindTracesClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanReaderPlugin_FindTracesClient is an autogenerated mock type for the SpanReaderPlugin_FindTracesClient type\ntype SpanReaderPlugin_FindTracesClient struct {\n\tmock.Mock\n}\n\ntype SpanReaderPlugin_FindTracesClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanReaderPlugin_FindTracesClient) EXPECT() *SpanReaderPlugin_FindTracesClient_Expecter {\n\treturn &SpanReaderPlugin_FindTracesClient_Expecter{mock: &_m.Mock}\n}\n\n// CloseSend provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) CloseSend() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CloseSend\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'\ntype SpanReaderPlugin_FindTracesClient_CloseSend_Call struct {\n\t*mock.Call\n}\n\n// CloseSend is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) CloseSend() *SpanReaderPlugin_FindTracesClient_CloseSend_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_CloseSend_Call{Call: _e.mock.On(\"CloseSend\")}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_CloseSend_Call) Run(run func()) *SpanReaderPlugin_FindTracesClient_CloseSend_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_CloseSend_Call) Return(err error) *SpanReaderPlugin_FindTracesClient_CloseSend_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_CloseSend_Call) RunAndReturn(run func() error) *SpanReaderPlugin_FindTracesClient_CloseSend_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Context provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype SpanReaderPlugin_FindTracesClient_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) Context() *SpanReaderPlugin_FindTracesClient_Context_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Context_Call) Run(run func()) *SpanReaderPlugin_FindTracesClient_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Context_Call) Return(context1 context.Context) *SpanReaderPlugin_FindTracesClient_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Context_Call) RunAndReturn(run func() context.Context) *SpanReaderPlugin_FindTracesClient_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Header provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) Header() (metadata.MD, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Header\")\n\t}\n\n\tvar r0 metadata.MD\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (metadata.MD, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPlugin_FindTracesClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'\ntype SpanReaderPlugin_FindTracesClient_Header_Call struct {\n\t*mock.Call\n}\n\n// Header is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) Header() *SpanReaderPlugin_FindTracesClient_Header_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_Header_Call{Call: _e.mock.On(\"Header\")}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Header_Call) Run(run func()) *SpanReaderPlugin_FindTracesClient_Header_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Header_Call) Return(mD metadata.MD, err error) *SpanReaderPlugin_FindTracesClient_Header_Call {\n\t_c.Call.Return(mD, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Header_Call) RunAndReturn(run func() (metadata.MD, error)) *SpanReaderPlugin_FindTracesClient_Header_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Recv provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) Recv() (*storage_v1.SpansResponseChunk, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Recv\")\n\t}\n\n\tvar r0 *storage_v1.SpansResponseChunk\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (*storage_v1.SpansResponseChunk, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() *storage_v1.SpansResponseChunk); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.SpansResponseChunk)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPlugin_FindTracesClient_Recv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Recv'\ntype SpanReaderPlugin_FindTracesClient_Recv_Call struct {\n\t*mock.Call\n}\n\n// Recv is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) Recv() *SpanReaderPlugin_FindTracesClient_Recv_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_Recv_Call{Call: _e.mock.On(\"Recv\")}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Recv_Call) Run(run func()) *SpanReaderPlugin_FindTracesClient_Recv_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Recv_Call) Return(spansResponseChunk *storage_v1.SpansResponseChunk, err error) *SpanReaderPlugin_FindTracesClient_Recv_Call {\n\t_c.Call.Return(spansResponseChunk, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Recv_Call) RunAndReturn(run func() (*storage_v1.SpansResponseChunk, error)) *SpanReaderPlugin_FindTracesClient_Recv_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype SpanReaderPlugin_FindTracesClient_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) RecvMsg(m interface{}) *SpanReaderPlugin_FindTracesClient_RecvMsg_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_RecvMsg_Call) Run(run func(m any)) *SpanReaderPlugin_FindTracesClient_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_RecvMsg_Call) Return(err error) *SpanReaderPlugin_FindTracesClient_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_RecvMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_FindTracesClient_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype SpanReaderPlugin_FindTracesClient_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) SendMsg(m interface{}) *SpanReaderPlugin_FindTracesClient_SendMsg_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_SendMsg_Call) Run(run func(m any)) *SpanReaderPlugin_FindTracesClient_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_SendMsg_Call) Return(err error) *SpanReaderPlugin_FindTracesClient_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_SendMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_FindTracesClient_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Trailer provides a mock function for the type SpanReaderPlugin_FindTracesClient\nfunc (_mock *SpanReaderPlugin_FindTracesClient) Trailer() metadata.MD {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Trailer\")\n\t}\n\n\tvar r0 metadata.MD\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'\ntype SpanReaderPlugin_FindTracesClient_Trailer_Call struct {\n\t*mock.Call\n}\n\n// Trailer is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_FindTracesClient_Expecter) Trailer() *SpanReaderPlugin_FindTracesClient_Trailer_Call {\n\treturn &SpanReaderPlugin_FindTracesClient_Trailer_Call{Call: _e.mock.On(\"Trailer\")}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Trailer_Call) Run(run func()) *SpanReaderPlugin_FindTracesClient_Trailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Trailer_Call) Return(mD metadata.MD) *SpanReaderPlugin_FindTracesClient_Trailer_Call {\n\t_c.Call.Return(mD)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesClient_Trailer_Call) RunAndReturn(run func() metadata.MD) *SpanReaderPlugin_FindTracesClient_Trailer_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSpanReaderPluginServer creates a new instance of SpanReaderPluginServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanReaderPluginServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanReaderPluginServer {\n\tmock := &SpanReaderPluginServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanReaderPluginServer is an autogenerated mock type for the SpanReaderPluginServer type\ntype SpanReaderPluginServer struct {\n\tmock.Mock\n}\n\ntype SpanReaderPluginServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanReaderPluginServer) EXPECT() *SpanReaderPluginServer_Expecter {\n\treturn &SpanReaderPluginServer_Expecter{mock: &_m.Mock}\n}\n\n// FindTraceIDs provides a mock function for the type SpanReaderPluginServer\nfunc (_mock *SpanReaderPluginServer) FindTraceIDs(context1 context.Context, findTraceIDsRequest *storage_v1.FindTraceIDsRequest) (*storage_v1.FindTraceIDsResponse, error) {\n\tret := _mock.Called(context1, findTraceIDsRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraceIDs\")\n\t}\n\n\tvar r0 *storage_v1.FindTraceIDsResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.FindTraceIDsRequest) (*storage_v1.FindTraceIDsResponse, error)); ok {\n\t\treturn returnFunc(context1, findTraceIDsRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.FindTraceIDsRequest) *storage_v1.FindTraceIDsResponse); ok {\n\t\tr0 = returnFunc(context1, findTraceIDsRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.FindTraceIDsResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.FindTraceIDsRequest) error); ok {\n\t\tr1 = returnFunc(context1, findTraceIDsRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginServer_FindTraceIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraceIDs'\ntype SpanReaderPluginServer_FindTraceIDs_Call struct {\n\t*mock.Call\n}\n\n// FindTraceIDs is a helper method to define mock.On call\n//   - context1 context.Context\n//   - findTraceIDsRequest *storage_v1.FindTraceIDsRequest\nfunc (_e *SpanReaderPluginServer_Expecter) FindTraceIDs(context1 interface{}, findTraceIDsRequest interface{}) *SpanReaderPluginServer_FindTraceIDs_Call {\n\treturn &SpanReaderPluginServer_FindTraceIDs_Call{Call: _e.mock.On(\"FindTraceIDs\", context1, findTraceIDsRequest)}\n}\n\nfunc (_c *SpanReaderPluginServer_FindTraceIDs_Call) Run(run func(context1 context.Context, findTraceIDsRequest *storage_v1.FindTraceIDsRequest)) *SpanReaderPluginServer_FindTraceIDs_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.FindTraceIDsRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.FindTraceIDsRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_FindTraceIDs_Call) Return(findTraceIDsResponse *storage_v1.FindTraceIDsResponse, err error) *SpanReaderPluginServer_FindTraceIDs_Call {\n\t_c.Call.Return(findTraceIDsResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_FindTraceIDs_Call) RunAndReturn(run func(context1 context.Context, findTraceIDsRequest *storage_v1.FindTraceIDsRequest) (*storage_v1.FindTraceIDsResponse, error)) *SpanReaderPluginServer_FindTraceIDs_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// FindTraces provides a mock function for the type SpanReaderPluginServer\nfunc (_mock *SpanReaderPluginServer) FindTraces(findTracesRequest *storage_v1.FindTracesRequest, spanReaderPlugin_FindTracesServer storage_v1.SpanReaderPlugin_FindTracesServer) error {\n\tret := _mock.Called(findTracesRequest, spanReaderPlugin_FindTracesServer)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraces\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.FindTracesRequest, storage_v1.SpanReaderPlugin_FindTracesServer) error); ok {\n\t\tr0 = returnFunc(findTracesRequest, spanReaderPlugin_FindTracesServer)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPluginServer_FindTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraces'\ntype SpanReaderPluginServer_FindTraces_Call struct {\n\t*mock.Call\n}\n\n// FindTraces is a helper method to define mock.On call\n//   - findTracesRequest *storage_v1.FindTracesRequest\n//   - spanReaderPlugin_FindTracesServer storage_v1.SpanReaderPlugin_FindTracesServer\nfunc (_e *SpanReaderPluginServer_Expecter) FindTraces(findTracesRequest interface{}, spanReaderPlugin_FindTracesServer interface{}) *SpanReaderPluginServer_FindTraces_Call {\n\treturn &SpanReaderPluginServer_FindTraces_Call{Call: _e.mock.On(\"FindTraces\", findTracesRequest, spanReaderPlugin_FindTracesServer)}\n}\n\nfunc (_c *SpanReaderPluginServer_FindTraces_Call) Run(run func(findTracesRequest *storage_v1.FindTracesRequest, spanReaderPlugin_FindTracesServer storage_v1.SpanReaderPlugin_FindTracesServer)) *SpanReaderPluginServer_FindTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.FindTracesRequest\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.FindTracesRequest)\n\t\t}\n\t\tvar arg1 storage_v1.SpanReaderPlugin_FindTracesServer\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(storage_v1.SpanReaderPlugin_FindTracesServer)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_FindTraces_Call) Return(err error) *SpanReaderPluginServer_FindTraces_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_FindTraces_Call) RunAndReturn(run func(findTracesRequest *storage_v1.FindTracesRequest, spanReaderPlugin_FindTracesServer storage_v1.SpanReaderPlugin_FindTracesServer) error) *SpanReaderPluginServer_FindTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperations provides a mock function for the type SpanReaderPluginServer\nfunc (_mock *SpanReaderPluginServer) GetOperations(context1 context.Context, getOperationsRequest *storage_v1.GetOperationsRequest) (*storage_v1.GetOperationsResponse, error) {\n\tret := _mock.Called(context1, getOperationsRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperations\")\n\t}\n\n\tvar r0 *storage_v1.GetOperationsResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetOperationsRequest) (*storage_v1.GetOperationsResponse, error)); ok {\n\t\treturn returnFunc(context1, getOperationsRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetOperationsRequest) *storage_v1.GetOperationsResponse); ok {\n\t\tr0 = returnFunc(context1, getOperationsRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.GetOperationsResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetOperationsRequest) error); ok {\n\t\tr1 = returnFunc(context1, getOperationsRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginServer_GetOperations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperations'\ntype SpanReaderPluginServer_GetOperations_Call struct {\n\t*mock.Call\n}\n\n// GetOperations is a helper method to define mock.On call\n//   - context1 context.Context\n//   - getOperationsRequest *storage_v1.GetOperationsRequest\nfunc (_e *SpanReaderPluginServer_Expecter) GetOperations(context1 interface{}, getOperationsRequest interface{}) *SpanReaderPluginServer_GetOperations_Call {\n\treturn &SpanReaderPluginServer_GetOperations_Call{Call: _e.mock.On(\"GetOperations\", context1, getOperationsRequest)}\n}\n\nfunc (_c *SpanReaderPluginServer_GetOperations_Call) Run(run func(context1 context.Context, getOperationsRequest *storage_v1.GetOperationsRequest)) *SpanReaderPluginServer_GetOperations_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetOperationsRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetOperationsRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_GetOperations_Call) Return(getOperationsResponse *storage_v1.GetOperationsResponse, err error) *SpanReaderPluginServer_GetOperations_Call {\n\t_c.Call.Return(getOperationsResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_GetOperations_Call) RunAndReturn(run func(context1 context.Context, getOperationsRequest *storage_v1.GetOperationsRequest) (*storage_v1.GetOperationsResponse, error)) *SpanReaderPluginServer_GetOperations_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetServices provides a mock function for the type SpanReaderPluginServer\nfunc (_mock *SpanReaderPluginServer) GetServices(context1 context.Context, getServicesRequest *storage_v1.GetServicesRequest) (*storage_v1.GetServicesResponse, error) {\n\tret := _mock.Called(context1, getServicesRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetServices\")\n\t}\n\n\tvar r0 *storage_v1.GetServicesResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetServicesRequest) (*storage_v1.GetServicesResponse, error)); ok {\n\t\treturn returnFunc(context1, getServicesRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetServicesRequest) *storage_v1.GetServicesResponse); ok {\n\t\tr0 = returnFunc(context1, getServicesRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.GetServicesResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetServicesRequest) error); ok {\n\t\tr1 = returnFunc(context1, getServicesRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SpanReaderPluginServer_GetServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServices'\ntype SpanReaderPluginServer_GetServices_Call struct {\n\t*mock.Call\n}\n\n// GetServices is a helper method to define mock.On call\n//   - context1 context.Context\n//   - getServicesRequest *storage_v1.GetServicesRequest\nfunc (_e *SpanReaderPluginServer_Expecter) GetServices(context1 interface{}, getServicesRequest interface{}) *SpanReaderPluginServer_GetServices_Call {\n\treturn &SpanReaderPluginServer_GetServices_Call{Call: _e.mock.On(\"GetServices\", context1, getServicesRequest)}\n}\n\nfunc (_c *SpanReaderPluginServer_GetServices_Call) Run(run func(context1 context.Context, getServicesRequest *storage_v1.GetServicesRequest)) *SpanReaderPluginServer_GetServices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetServicesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetServicesRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_GetServices_Call) Return(getServicesResponse *storage_v1.GetServicesResponse, err error) *SpanReaderPluginServer_GetServices_Call {\n\t_c.Call.Return(getServicesResponse, err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_GetServices_Call) RunAndReturn(run func(context1 context.Context, getServicesRequest *storage_v1.GetServicesRequest) (*storage_v1.GetServicesResponse, error)) *SpanReaderPluginServer_GetServices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetTrace provides a mock function for the type SpanReaderPluginServer\nfunc (_mock *SpanReaderPluginServer) GetTrace(getTraceRequest *storage_v1.GetTraceRequest, spanReaderPlugin_GetTraceServer storage_v1.SpanReaderPlugin_GetTraceServer) error {\n\tret := _mock.Called(getTraceRequest, spanReaderPlugin_GetTraceServer)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTrace\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.GetTraceRequest, storage_v1.SpanReaderPlugin_GetTraceServer) error); ok {\n\t\tr0 = returnFunc(getTraceRequest, spanReaderPlugin_GetTraceServer)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPluginServer_GetTrace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTrace'\ntype SpanReaderPluginServer_GetTrace_Call struct {\n\t*mock.Call\n}\n\n// GetTrace is a helper method to define mock.On call\n//   - getTraceRequest *storage_v1.GetTraceRequest\n//   - spanReaderPlugin_GetTraceServer storage_v1.SpanReaderPlugin_GetTraceServer\nfunc (_e *SpanReaderPluginServer_Expecter) GetTrace(getTraceRequest interface{}, spanReaderPlugin_GetTraceServer interface{}) *SpanReaderPluginServer_GetTrace_Call {\n\treturn &SpanReaderPluginServer_GetTrace_Call{Call: _e.mock.On(\"GetTrace\", getTraceRequest, spanReaderPlugin_GetTraceServer)}\n}\n\nfunc (_c *SpanReaderPluginServer_GetTrace_Call) Run(run func(getTraceRequest *storage_v1.GetTraceRequest, spanReaderPlugin_GetTraceServer storage_v1.SpanReaderPlugin_GetTraceServer)) *SpanReaderPluginServer_GetTrace_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.GetTraceRequest\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.GetTraceRequest)\n\t\t}\n\t\tvar arg1 storage_v1.SpanReaderPlugin_GetTraceServer\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(storage_v1.SpanReaderPlugin_GetTraceServer)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_GetTrace_Call) Return(err error) *SpanReaderPluginServer_GetTrace_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPluginServer_GetTrace_Call) RunAndReturn(run func(getTraceRequest *storage_v1.GetTraceRequest, spanReaderPlugin_GetTraceServer storage_v1.SpanReaderPlugin_GetTraceServer) error) *SpanReaderPluginServer_GetTrace_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSpanReaderPlugin_GetTraceServer creates a new instance of SpanReaderPlugin_GetTraceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanReaderPlugin_GetTraceServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanReaderPlugin_GetTraceServer {\n\tmock := &SpanReaderPlugin_GetTraceServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanReaderPlugin_GetTraceServer is an autogenerated mock type for the SpanReaderPlugin_GetTraceServer type\ntype SpanReaderPlugin_GetTraceServer struct {\n\tmock.Mock\n}\n\ntype SpanReaderPlugin_GetTraceServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanReaderPlugin_GetTraceServer) EXPECT() *SpanReaderPlugin_GetTraceServer_Expecter {\n\treturn &SpanReaderPlugin_GetTraceServer_Expecter{mock: &_m.Mock}\n}\n\n// Context provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceServer_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype SpanReaderPlugin_GetTraceServer_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) Context() *SpanReaderPlugin_GetTraceServer_Context_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_Context_Call) Run(run func()) *SpanReaderPlugin_GetTraceServer_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_Context_Call) Return(context1 context.Context) *SpanReaderPlugin_GetTraceServer_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_Context_Call) RunAndReturn(run func() context.Context) *SpanReaderPlugin_GetTraceServer_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceServer_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype SpanReaderPlugin_GetTraceServer_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) RecvMsg(m interface{}) *SpanReaderPlugin_GetTraceServer_RecvMsg_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_RecvMsg_Call) Run(run func(m any)) *SpanReaderPlugin_GetTraceServer_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_RecvMsg_Call) Return(err error) *SpanReaderPlugin_GetTraceServer_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_RecvMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_GetTraceServer_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Send provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) Send(spansResponseChunk *storage_v1.SpansResponseChunk) error {\n\tret := _mock.Called(spansResponseChunk)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Send\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.SpansResponseChunk) error); ok {\n\t\tr0 = returnFunc(spansResponseChunk)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceServer_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'\ntype SpanReaderPlugin_GetTraceServer_Send_Call struct {\n\t*mock.Call\n}\n\n// Send is a helper method to define mock.On call\n//   - spansResponseChunk *storage_v1.SpansResponseChunk\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) Send(spansResponseChunk interface{}) *SpanReaderPlugin_GetTraceServer_Send_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_Send_Call{Call: _e.mock.On(\"Send\", spansResponseChunk)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_Send_Call) Run(run func(spansResponseChunk *storage_v1.SpansResponseChunk)) *SpanReaderPlugin_GetTraceServer_Send_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.SpansResponseChunk\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.SpansResponseChunk)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_Send_Call) Return(err error) *SpanReaderPlugin_GetTraceServer_Send_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_Send_Call) RunAndReturn(run func(spansResponseChunk *storage_v1.SpansResponseChunk) error) *SpanReaderPlugin_GetTraceServer_Send_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendHeader provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) SendHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceServer_SendHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendHeader'\ntype SpanReaderPlugin_GetTraceServer_SendHeader_Call struct {\n\t*mock.Call\n}\n\n// SendHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) SendHeader(mD interface{}) *SpanReaderPlugin_GetTraceServer_SendHeader_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_SendHeader_Call{Call: _e.mock.On(\"SendHeader\", mD)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SendHeader_Call) Run(run func(mD metadata.MD)) *SpanReaderPlugin_GetTraceServer_SendHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SendHeader_Call) Return(err error) *SpanReaderPlugin_GetTraceServer_SendHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SendHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *SpanReaderPlugin_GetTraceServer_SendHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceServer_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype SpanReaderPlugin_GetTraceServer_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) SendMsg(m interface{}) *SpanReaderPlugin_GetTraceServer_SendMsg_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SendMsg_Call) Run(run func(m any)) *SpanReaderPlugin_GetTraceServer_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SendMsg_Call) Return(err error) *SpanReaderPlugin_GetTraceServer_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SendMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_GetTraceServer_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetHeader provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) SetHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SetHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_GetTraceServer_SetHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetHeader'\ntype SpanReaderPlugin_GetTraceServer_SetHeader_Call struct {\n\t*mock.Call\n}\n\n// SetHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) SetHeader(mD interface{}) *SpanReaderPlugin_GetTraceServer_SetHeader_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_SetHeader_Call{Call: _e.mock.On(\"SetHeader\", mD)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SetHeader_Call) Run(run func(mD metadata.MD)) *SpanReaderPlugin_GetTraceServer_SetHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SetHeader_Call) Return(err error) *SpanReaderPlugin_GetTraceServer_SetHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SetHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *SpanReaderPlugin_GetTraceServer_SetHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetTrailer provides a mock function for the type SpanReaderPlugin_GetTraceServer\nfunc (_mock *SpanReaderPlugin_GetTraceServer) SetTrailer(mD metadata.MD) {\n\t_mock.Called(mD)\n\treturn\n}\n\n// SpanReaderPlugin_GetTraceServer_SetTrailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetTrailer'\ntype SpanReaderPlugin_GetTraceServer_SetTrailer_Call struct {\n\t*mock.Call\n}\n\n// SetTrailer is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *SpanReaderPlugin_GetTraceServer_Expecter) SetTrailer(mD interface{}) *SpanReaderPlugin_GetTraceServer_SetTrailer_Call {\n\treturn &SpanReaderPlugin_GetTraceServer_SetTrailer_Call{Call: _e.mock.On(\"SetTrailer\", mD)}\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SetTrailer_Call) Run(run func(mD metadata.MD)) *SpanReaderPlugin_GetTraceServer_SetTrailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SetTrailer_Call) Return() *SpanReaderPlugin_GetTraceServer_SetTrailer_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_GetTraceServer_SetTrailer_Call) RunAndReturn(run func(mD metadata.MD)) *SpanReaderPlugin_GetTraceServer_SetTrailer_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// NewSpanReaderPlugin_FindTracesServer creates a new instance of SpanReaderPlugin_FindTracesServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSpanReaderPlugin_FindTracesServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SpanReaderPlugin_FindTracesServer {\n\tmock := &SpanReaderPlugin_FindTracesServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SpanReaderPlugin_FindTracesServer is an autogenerated mock type for the SpanReaderPlugin_FindTracesServer type\ntype SpanReaderPlugin_FindTracesServer struct {\n\tmock.Mock\n}\n\ntype SpanReaderPlugin_FindTracesServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SpanReaderPlugin_FindTracesServer) EXPECT() *SpanReaderPlugin_FindTracesServer_Expecter {\n\treturn &SpanReaderPlugin_FindTracesServer_Expecter{mock: &_m.Mock}\n}\n\n// Context provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesServer_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype SpanReaderPlugin_FindTracesServer_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) Context() *SpanReaderPlugin_FindTracesServer_Context_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_Context_Call) Run(run func()) *SpanReaderPlugin_FindTracesServer_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_Context_Call) Return(context1 context.Context) *SpanReaderPlugin_FindTracesServer_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_Context_Call) RunAndReturn(run func() context.Context) *SpanReaderPlugin_FindTracesServer_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesServer_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype SpanReaderPlugin_FindTracesServer_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) RecvMsg(m interface{}) *SpanReaderPlugin_FindTracesServer_RecvMsg_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_RecvMsg_Call) Run(run func(m any)) *SpanReaderPlugin_FindTracesServer_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_RecvMsg_Call) Return(err error) *SpanReaderPlugin_FindTracesServer_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_RecvMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_FindTracesServer_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Send provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) Send(spansResponseChunk *storage_v1.SpansResponseChunk) error {\n\tret := _mock.Called(spansResponseChunk)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Send\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.SpansResponseChunk) error); ok {\n\t\tr0 = returnFunc(spansResponseChunk)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesServer_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'\ntype SpanReaderPlugin_FindTracesServer_Send_Call struct {\n\t*mock.Call\n}\n\n// Send is a helper method to define mock.On call\n//   - spansResponseChunk *storage_v1.SpansResponseChunk\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) Send(spansResponseChunk interface{}) *SpanReaderPlugin_FindTracesServer_Send_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_Send_Call{Call: _e.mock.On(\"Send\", spansResponseChunk)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_Send_Call) Run(run func(spansResponseChunk *storage_v1.SpansResponseChunk)) *SpanReaderPlugin_FindTracesServer_Send_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.SpansResponseChunk\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.SpansResponseChunk)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_Send_Call) Return(err error) *SpanReaderPlugin_FindTracesServer_Send_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_Send_Call) RunAndReturn(run func(spansResponseChunk *storage_v1.SpansResponseChunk) error) *SpanReaderPlugin_FindTracesServer_Send_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendHeader provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) SendHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesServer_SendHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendHeader'\ntype SpanReaderPlugin_FindTracesServer_SendHeader_Call struct {\n\t*mock.Call\n}\n\n// SendHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) SendHeader(mD interface{}) *SpanReaderPlugin_FindTracesServer_SendHeader_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_SendHeader_Call{Call: _e.mock.On(\"SendHeader\", mD)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SendHeader_Call) Run(run func(mD metadata.MD)) *SpanReaderPlugin_FindTracesServer_SendHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SendHeader_Call) Return(err error) *SpanReaderPlugin_FindTracesServer_SendHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SendHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *SpanReaderPlugin_FindTracesServer_SendHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesServer_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype SpanReaderPlugin_FindTracesServer_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) SendMsg(m interface{}) *SpanReaderPlugin_FindTracesServer_SendMsg_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SendMsg_Call) Run(run func(m any)) *SpanReaderPlugin_FindTracesServer_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SendMsg_Call) Return(err error) *SpanReaderPlugin_FindTracesServer_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SendMsg_Call) RunAndReturn(run func(m any) error) *SpanReaderPlugin_FindTracesServer_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetHeader provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) SetHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SetHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// SpanReaderPlugin_FindTracesServer_SetHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetHeader'\ntype SpanReaderPlugin_FindTracesServer_SetHeader_Call struct {\n\t*mock.Call\n}\n\n// SetHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) SetHeader(mD interface{}) *SpanReaderPlugin_FindTracesServer_SetHeader_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_SetHeader_Call{Call: _e.mock.On(\"SetHeader\", mD)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SetHeader_Call) Run(run func(mD metadata.MD)) *SpanReaderPlugin_FindTracesServer_SetHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SetHeader_Call) Return(err error) *SpanReaderPlugin_FindTracesServer_SetHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SetHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *SpanReaderPlugin_FindTracesServer_SetHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetTrailer provides a mock function for the type SpanReaderPlugin_FindTracesServer\nfunc (_mock *SpanReaderPlugin_FindTracesServer) SetTrailer(mD metadata.MD) {\n\t_mock.Called(mD)\n\treturn\n}\n\n// SpanReaderPlugin_FindTracesServer_SetTrailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetTrailer'\ntype SpanReaderPlugin_FindTracesServer_SetTrailer_Call struct {\n\t*mock.Call\n}\n\n// SetTrailer is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *SpanReaderPlugin_FindTracesServer_Expecter) SetTrailer(mD interface{}) *SpanReaderPlugin_FindTracesServer_SetTrailer_Call {\n\treturn &SpanReaderPlugin_FindTracesServer_SetTrailer_Call{Call: _e.mock.On(\"SetTrailer\", mD)}\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SetTrailer_Call) Run(run func(mD metadata.MD)) *SpanReaderPlugin_FindTracesServer_SetTrailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SetTrailer_Call) Return() *SpanReaderPlugin_FindTracesServer_SetTrailer_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *SpanReaderPlugin_FindTracesServer_SetTrailer_Call) RunAndReturn(run func(mD metadata.MD)) *SpanReaderPlugin_FindTracesServer_SetTrailer_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// NewArchiveSpanWriterPluginClient creates a new instance of ArchiveSpanWriterPluginClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveSpanWriterPluginClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveSpanWriterPluginClient {\n\tmock := &ArchiveSpanWriterPluginClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveSpanWriterPluginClient is an autogenerated mock type for the ArchiveSpanWriterPluginClient type\ntype ArchiveSpanWriterPluginClient struct {\n\tmock.Mock\n}\n\ntype ArchiveSpanWriterPluginClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveSpanWriterPluginClient) EXPECT() *ArchiveSpanWriterPluginClient_Expecter {\n\treturn &ArchiveSpanWriterPluginClient_Expecter{mock: &_m.Mock}\n}\n\n// WriteArchiveSpan provides a mock function for the type ArchiveSpanWriterPluginClient\nfunc (_mock *ArchiveSpanWriterPluginClient) WriteArchiveSpan(ctx context.Context, in *storage_v1.WriteSpanRequest, opts ...grpc.CallOption) (*storage_v1.WriteSpanResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteArchiveSpan\")\n\t}\n\n\tvar r0 *storage_v1.WriteSpanResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest, ...grpc.CallOption) (*storage_v1.WriteSpanResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest, ...grpc.CallOption) *storage_v1.WriteSpanResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.WriteSpanResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.WriteSpanRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteArchiveSpan'\ntype ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call struct {\n\t*mock.Call\n}\n\n// WriteArchiveSpan is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.WriteSpanRequest\n//   - opts ...grpc.CallOption\nfunc (_e *ArchiveSpanWriterPluginClient_Expecter) WriteArchiveSpan(ctx interface{}, in interface{}, opts ...interface{}) *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call {\n\treturn &ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call{Call: _e.mock.On(\"WriteArchiveSpan\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call) Run(run func(ctx context.Context, in *storage_v1.WriteSpanRequest, opts ...grpc.CallOption)) *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.WriteSpanRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.WriteSpanRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call) Return(writeSpanResponse *storage_v1.WriteSpanResponse, err error) *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call {\n\t_c.Call.Return(writeSpanResponse, err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.WriteSpanRequest, opts ...grpc.CallOption) (*storage_v1.WriteSpanResponse, error)) *ArchiveSpanWriterPluginClient_WriteArchiveSpan_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewArchiveSpanWriterPluginServer creates a new instance of ArchiveSpanWriterPluginServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveSpanWriterPluginServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveSpanWriterPluginServer {\n\tmock := &ArchiveSpanWriterPluginServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveSpanWriterPluginServer is an autogenerated mock type for the ArchiveSpanWriterPluginServer type\ntype ArchiveSpanWriterPluginServer struct {\n\tmock.Mock\n}\n\ntype ArchiveSpanWriterPluginServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveSpanWriterPluginServer) EXPECT() *ArchiveSpanWriterPluginServer_Expecter {\n\treturn &ArchiveSpanWriterPluginServer_Expecter{mock: &_m.Mock}\n}\n\n// WriteArchiveSpan provides a mock function for the type ArchiveSpanWriterPluginServer\nfunc (_mock *ArchiveSpanWriterPluginServer) WriteArchiveSpan(context1 context.Context, writeSpanRequest *storage_v1.WriteSpanRequest) (*storage_v1.WriteSpanResponse, error) {\n\tret := _mock.Called(context1, writeSpanRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteArchiveSpan\")\n\t}\n\n\tvar r0 *storage_v1.WriteSpanResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest) (*storage_v1.WriteSpanResponse, error)); ok {\n\t\treturn returnFunc(context1, writeSpanRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.WriteSpanRequest) *storage_v1.WriteSpanResponse); ok {\n\t\tr0 = returnFunc(context1, writeSpanRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.WriteSpanResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.WriteSpanRequest) error); ok {\n\t\tr1 = returnFunc(context1, writeSpanRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteArchiveSpan'\ntype ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call struct {\n\t*mock.Call\n}\n\n// WriteArchiveSpan is a helper method to define mock.On call\n//   - context1 context.Context\n//   - writeSpanRequest *storage_v1.WriteSpanRequest\nfunc (_e *ArchiveSpanWriterPluginServer_Expecter) WriteArchiveSpan(context1 interface{}, writeSpanRequest interface{}) *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call {\n\treturn &ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call{Call: _e.mock.On(\"WriteArchiveSpan\", context1, writeSpanRequest)}\n}\n\nfunc (_c *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call) Run(run func(context1 context.Context, writeSpanRequest *storage_v1.WriteSpanRequest)) *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.WriteSpanRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.WriteSpanRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call) Return(writeSpanResponse *storage_v1.WriteSpanResponse, err error) *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call {\n\t_c.Call.Return(writeSpanResponse, err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call) RunAndReturn(run func(context1 context.Context, writeSpanRequest *storage_v1.WriteSpanRequest) (*storage_v1.WriteSpanResponse, error)) *ArchiveSpanWriterPluginServer_WriteArchiveSpan_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewArchiveSpanReaderPluginClient creates a new instance of ArchiveSpanReaderPluginClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveSpanReaderPluginClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveSpanReaderPluginClient {\n\tmock := &ArchiveSpanReaderPluginClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveSpanReaderPluginClient is an autogenerated mock type for the ArchiveSpanReaderPluginClient type\ntype ArchiveSpanReaderPluginClient struct {\n\tmock.Mock\n}\n\ntype ArchiveSpanReaderPluginClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveSpanReaderPluginClient) EXPECT() *ArchiveSpanReaderPluginClient_Expecter {\n\treturn &ArchiveSpanReaderPluginClient_Expecter{mock: &_m.Mock}\n}\n\n// GetArchiveTrace provides a mock function for the type ArchiveSpanReaderPluginClient\nfunc (_mock *ArchiveSpanReaderPluginClient) GetArchiveTrace(ctx context.Context, in *storage_v1.GetTraceRequest, opts ...grpc.CallOption) (storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetArchiveTrace\")\n\t}\n\n\tvar r0 storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetTraceRequest, ...grpc.CallOption) (storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetTraceRequest, ...grpc.CallOption) storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetTraceRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// ArchiveSpanReaderPluginClient_GetArchiveTrace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetArchiveTrace'\ntype ArchiveSpanReaderPluginClient_GetArchiveTrace_Call struct {\n\t*mock.Call\n}\n\n// GetArchiveTrace is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.GetTraceRequest\n//   - opts ...grpc.CallOption\nfunc (_e *ArchiveSpanReaderPluginClient_Expecter) GetArchiveTrace(ctx interface{}, in interface{}, opts ...interface{}) *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call {\n\treturn &ArchiveSpanReaderPluginClient_GetArchiveTrace_Call{Call: _e.mock.On(\"GetArchiveTrace\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call) Run(run func(ctx context.Context, in *storage_v1.GetTraceRequest, opts ...grpc.CallOption)) *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetTraceRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetTraceRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call) Return(archiveSpanReaderPlugin_GetArchiveTraceClient storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient, err error) *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call {\n\t_c.Call.Return(archiveSpanReaderPlugin_GetArchiveTraceClient, err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.GetTraceRequest, opts ...grpc.CallOption) (storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceClient, error)) *ArchiveSpanReaderPluginClient_GetArchiveTrace_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewArchiveSpanReaderPlugin_GetArchiveTraceClient creates a new instance of ArchiveSpanReaderPlugin_GetArchiveTraceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveSpanReaderPlugin_GetArchiveTraceClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveSpanReaderPlugin_GetArchiveTraceClient {\n\tmock := &ArchiveSpanReaderPlugin_GetArchiveTraceClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient is an autogenerated mock type for the ArchiveSpanReaderPlugin_GetArchiveTraceClient type\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient struct {\n\tmock.Mock\n}\n\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveSpanReaderPlugin_GetArchiveTraceClient) EXPECT() *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter{mock: &_m.Mock}\n}\n\n// CloseSend provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) CloseSend() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CloseSend\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call struct {\n\t*mock.Call\n}\n\n// CloseSend is a helper method to define mock.On call\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) CloseSend() *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call{Call: _e.mock.On(\"CloseSend\")}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call) Run(run func()) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call) RunAndReturn(run func() error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_CloseSend_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Context provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) Context() *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call) Run(run func()) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call) Return(context1 context.Context) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call) RunAndReturn(run func() context.Context) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Header provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) Header() (metadata.MD, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Header\")\n\t}\n\n\tvar r0 metadata.MD\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (metadata.MD, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call struct {\n\t*mock.Call\n}\n\n// Header is a helper method to define mock.On call\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) Header() *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call{Call: _e.mock.On(\"Header\")}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call) Run(run func()) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call) Return(mD metadata.MD, err error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call {\n\t_c.Call.Return(mD, err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call) RunAndReturn(run func() (metadata.MD, error)) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Header_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Recv provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) Recv() (*storage_v1.SpansResponseChunk, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Recv\")\n\t}\n\n\tvar r0 *storage_v1.SpansResponseChunk\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (*storage_v1.SpansResponseChunk, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() *storage_v1.SpansResponseChunk); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.SpansResponseChunk)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Recv'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call struct {\n\t*mock.Call\n}\n\n// Recv is a helper method to define mock.On call\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) Recv() *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call{Call: _e.mock.On(\"Recv\")}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call) Run(run func()) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call) Return(spansResponseChunk *storage_v1.SpansResponseChunk, err error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call {\n\t_c.Call.Return(spansResponseChunk, err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call) RunAndReturn(run func() (*storage_v1.SpansResponseChunk, error)) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Recv_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) RecvMsg(m interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call) Run(run func(m any)) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call) RunAndReturn(run func(m any) error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) SendMsg(m interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call) Run(run func(m any)) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call) RunAndReturn(run func(m any) error) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Trailer provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceClient\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceClient) Trailer() metadata.MD {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Trailer\")\n\t}\n\n\tvar r0 metadata.MD\n\tif returnFunc, ok := ret.Get(0).(func() metadata.MD); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metadata.MD)\n\t\t}\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call struct {\n\t*mock.Call\n}\n\n// Trailer is a helper method to define mock.On call\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Expecter) Trailer() *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call{Call: _e.mock.On(\"Trailer\")}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call) Run(run func()) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call) Return(mD metadata.MD) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call {\n\t_c.Call.Return(mD)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call) RunAndReturn(run func() metadata.MD) *ArchiveSpanReaderPlugin_GetArchiveTraceClient_Trailer_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewArchiveSpanReaderPluginServer creates a new instance of ArchiveSpanReaderPluginServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveSpanReaderPluginServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveSpanReaderPluginServer {\n\tmock := &ArchiveSpanReaderPluginServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveSpanReaderPluginServer is an autogenerated mock type for the ArchiveSpanReaderPluginServer type\ntype ArchiveSpanReaderPluginServer struct {\n\tmock.Mock\n}\n\ntype ArchiveSpanReaderPluginServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveSpanReaderPluginServer) EXPECT() *ArchiveSpanReaderPluginServer_Expecter {\n\treturn &ArchiveSpanReaderPluginServer_Expecter{mock: &_m.Mock}\n}\n\n// GetArchiveTrace provides a mock function for the type ArchiveSpanReaderPluginServer\nfunc (_mock *ArchiveSpanReaderPluginServer) GetArchiveTrace(getTraceRequest *storage_v1.GetTraceRequest, archiveSpanReaderPlugin_GetArchiveTraceServer storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer) error {\n\tret := _mock.Called(getTraceRequest, archiveSpanReaderPlugin_GetArchiveTraceServer)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetArchiveTrace\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.GetTraceRequest, storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer) error); ok {\n\t\tr0 = returnFunc(getTraceRequest, archiveSpanReaderPlugin_GetArchiveTraceServer)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPluginServer_GetArchiveTrace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetArchiveTrace'\ntype ArchiveSpanReaderPluginServer_GetArchiveTrace_Call struct {\n\t*mock.Call\n}\n\n// GetArchiveTrace is a helper method to define mock.On call\n//   - getTraceRequest *storage_v1.GetTraceRequest\n//   - archiveSpanReaderPlugin_GetArchiveTraceServer storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_e *ArchiveSpanReaderPluginServer_Expecter) GetArchiveTrace(getTraceRequest interface{}, archiveSpanReaderPlugin_GetArchiveTraceServer interface{}) *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call {\n\treturn &ArchiveSpanReaderPluginServer_GetArchiveTrace_Call{Call: _e.mock.On(\"GetArchiveTrace\", getTraceRequest, archiveSpanReaderPlugin_GetArchiveTraceServer)}\n}\n\nfunc (_c *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call) Run(run func(getTraceRequest *storage_v1.GetTraceRequest, archiveSpanReaderPlugin_GetArchiveTraceServer storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer)) *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.GetTraceRequest\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.GetTraceRequest)\n\t\t}\n\t\tvar arg1 storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call) Return(err error) *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call) RunAndReturn(run func(getTraceRequest *storage_v1.GetTraceRequest, archiveSpanReaderPlugin_GetArchiveTraceServer storage_v1.ArchiveSpanReaderPlugin_GetArchiveTraceServer) error) *ArchiveSpanReaderPluginServer_GetArchiveTrace_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewArchiveSpanReaderPlugin_GetArchiveTraceServer creates a new instance of ArchiveSpanReaderPlugin_GetArchiveTraceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveSpanReaderPlugin_GetArchiveTraceServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer {\n\tmock := &ArchiveSpanReaderPlugin_GetArchiveTraceServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer is an autogenerated mock type for the ArchiveSpanReaderPlugin_GetArchiveTraceServer type\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer struct {\n\tmock.Mock\n}\n\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveSpanReaderPlugin_GetArchiveTraceServer) EXPECT() *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter{mock: &_m.Mock}\n}\n\n// Context provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) Context() context.Context {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Context\")\n\t}\n\n\tvar r0 context.Context\n\tif returnFunc, ok := ret.Get(0).(func() context.Context); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(context.Context)\n\t\t}\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call struct {\n\t*mock.Call\n}\n\n// Context is a helper method to define mock.On call\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) Context() *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call{Call: _e.mock.On(\"Context\")}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call) Run(run func()) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call) Return(context1 context.Context) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call {\n\t_c.Call.Return(context1)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call) RunAndReturn(run func() context.Context) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Context_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// RecvMsg provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) RecvMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for RecvMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call struct {\n\t*mock.Call\n}\n\n// RecvMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) RecvMsg(m interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call{Call: _e.mock.On(\"RecvMsg\", m)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call) Run(run func(m any)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call) RunAndReturn(run func(m any) error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_RecvMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Send provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) Send(spansResponseChunk *storage_v1.SpansResponseChunk) error {\n\tret := _mock.Called(spansResponseChunk)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Send\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(*storage_v1.SpansResponseChunk) error); ok {\n\t\tr0 = returnFunc(spansResponseChunk)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call struct {\n\t*mock.Call\n}\n\n// Send is a helper method to define mock.On call\n//   - spansResponseChunk *storage_v1.SpansResponseChunk\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) Send(spansResponseChunk interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call{Call: _e.mock.On(\"Send\", spansResponseChunk)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call) Run(run func(spansResponseChunk *storage_v1.SpansResponseChunk)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *storage_v1.SpansResponseChunk\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*storage_v1.SpansResponseChunk)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call) RunAndReturn(run func(spansResponseChunk *storage_v1.SpansResponseChunk) error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Send_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendHeader provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) SendHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendHeader'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call struct {\n\t*mock.Call\n}\n\n// SendHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) SendHeader(mD interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call{Call: _e.mock.On(\"SendHeader\", mD)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call) Run(run func(mD metadata.MD)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SendMsg provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) SendMsg(m any) error {\n\tret := _mock.Called(m)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SendMsg\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(any) error); ok {\n\t\tr0 = returnFunc(m)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call struct {\n\t*mock.Call\n}\n\n// SendMsg is a helper method to define mock.On call\n//   - m any\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) SendMsg(m interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call{Call: _e.mock.On(\"SendMsg\", m)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call) Run(run func(m any)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call) RunAndReturn(run func(m any) error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SendMsg_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetHeader provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) SetHeader(mD metadata.MD) error {\n\tret := _mock.Called(mD)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for SetHeader\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(metadata.MD) error); ok {\n\t\tr0 = returnFunc(mD)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetHeader'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call struct {\n\t*mock.Call\n}\n\n// SetHeader is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) SetHeader(mD interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call{Call: _e.mock.On(\"SetHeader\", mD)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call) Run(run func(mD metadata.MD)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call) Return(err error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call) RunAndReturn(run func(mD metadata.MD) error) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetHeader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// SetTrailer provides a mock function for the type ArchiveSpanReaderPlugin_GetArchiveTraceServer\nfunc (_mock *ArchiveSpanReaderPlugin_GetArchiveTraceServer) SetTrailer(mD metadata.MD) {\n\t_mock.Called(mD)\n\treturn\n}\n\n// ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetTrailer'\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call struct {\n\t*mock.Call\n}\n\n// SetTrailer is a helper method to define mock.On call\n//   - mD metadata.MD\nfunc (_e *ArchiveSpanReaderPlugin_GetArchiveTraceServer_Expecter) SetTrailer(mD interface{}) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call {\n\treturn &ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call{Call: _e.mock.On(\"SetTrailer\", mD)}\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call) Run(run func(mD metadata.MD)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 metadata.MD\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(metadata.MD)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call) Return() *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call) RunAndReturn(run func(mD metadata.MD)) *ArchiveSpanReaderPlugin_GetArchiveTraceServer_SetTrailer_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// NewDependenciesReaderPluginClient creates a new instance of DependenciesReaderPluginClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewDependenciesReaderPluginClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *DependenciesReaderPluginClient {\n\tmock := &DependenciesReaderPluginClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// DependenciesReaderPluginClient is an autogenerated mock type for the DependenciesReaderPluginClient type\ntype DependenciesReaderPluginClient struct {\n\tmock.Mock\n}\n\ntype DependenciesReaderPluginClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *DependenciesReaderPluginClient) EXPECT() *DependenciesReaderPluginClient_Expecter {\n\treturn &DependenciesReaderPluginClient_Expecter{mock: &_m.Mock}\n}\n\n// GetDependencies provides a mock function for the type DependenciesReaderPluginClient\nfunc (_mock *DependenciesReaderPluginClient) GetDependencies(ctx context.Context, in *storage_v1.GetDependenciesRequest, opts ...grpc.CallOption) (*storage_v1.GetDependenciesResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetDependencies\")\n\t}\n\n\tvar r0 *storage_v1.GetDependenciesResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetDependenciesRequest, ...grpc.CallOption) (*storage_v1.GetDependenciesResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetDependenciesRequest, ...grpc.CallOption) *storage_v1.GetDependenciesResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.GetDependenciesResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetDependenciesRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// DependenciesReaderPluginClient_GetDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDependencies'\ntype DependenciesReaderPluginClient_GetDependencies_Call struct {\n\t*mock.Call\n}\n\n// GetDependencies is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.GetDependenciesRequest\n//   - opts ...grpc.CallOption\nfunc (_e *DependenciesReaderPluginClient_Expecter) GetDependencies(ctx interface{}, in interface{}, opts ...interface{}) *DependenciesReaderPluginClient_GetDependencies_Call {\n\treturn &DependenciesReaderPluginClient_GetDependencies_Call{Call: _e.mock.On(\"GetDependencies\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *DependenciesReaderPluginClient_GetDependencies_Call) Run(run func(ctx context.Context, in *storage_v1.GetDependenciesRequest, opts ...grpc.CallOption)) *DependenciesReaderPluginClient_GetDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetDependenciesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetDependenciesRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *DependenciesReaderPluginClient_GetDependencies_Call) Return(getDependenciesResponse *storage_v1.GetDependenciesResponse, err error) *DependenciesReaderPluginClient_GetDependencies_Call {\n\t_c.Call.Return(getDependenciesResponse, err)\n\treturn _c\n}\n\nfunc (_c *DependenciesReaderPluginClient_GetDependencies_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.GetDependenciesRequest, opts ...grpc.CallOption) (*storage_v1.GetDependenciesResponse, error)) *DependenciesReaderPluginClient_GetDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewDependenciesReaderPluginServer creates a new instance of DependenciesReaderPluginServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewDependenciesReaderPluginServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *DependenciesReaderPluginServer {\n\tmock := &DependenciesReaderPluginServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// DependenciesReaderPluginServer is an autogenerated mock type for the DependenciesReaderPluginServer type\ntype DependenciesReaderPluginServer struct {\n\tmock.Mock\n}\n\ntype DependenciesReaderPluginServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *DependenciesReaderPluginServer) EXPECT() *DependenciesReaderPluginServer_Expecter {\n\treturn &DependenciesReaderPluginServer_Expecter{mock: &_m.Mock}\n}\n\n// GetDependencies provides a mock function for the type DependenciesReaderPluginServer\nfunc (_mock *DependenciesReaderPluginServer) GetDependencies(context1 context.Context, getDependenciesRequest *storage_v1.GetDependenciesRequest) (*storage_v1.GetDependenciesResponse, error) {\n\tret := _mock.Called(context1, getDependenciesRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetDependencies\")\n\t}\n\n\tvar r0 *storage_v1.GetDependenciesResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetDependenciesRequest) (*storage_v1.GetDependenciesResponse, error)); ok {\n\t\treturn returnFunc(context1, getDependenciesRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.GetDependenciesRequest) *storage_v1.GetDependenciesResponse); ok {\n\t\tr0 = returnFunc(context1, getDependenciesRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.GetDependenciesResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.GetDependenciesRequest) error); ok {\n\t\tr1 = returnFunc(context1, getDependenciesRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// DependenciesReaderPluginServer_GetDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDependencies'\ntype DependenciesReaderPluginServer_GetDependencies_Call struct {\n\t*mock.Call\n}\n\n// GetDependencies is a helper method to define mock.On call\n//   - context1 context.Context\n//   - getDependenciesRequest *storage_v1.GetDependenciesRequest\nfunc (_e *DependenciesReaderPluginServer_Expecter) GetDependencies(context1 interface{}, getDependenciesRequest interface{}) *DependenciesReaderPluginServer_GetDependencies_Call {\n\treturn &DependenciesReaderPluginServer_GetDependencies_Call{Call: _e.mock.On(\"GetDependencies\", context1, getDependenciesRequest)}\n}\n\nfunc (_c *DependenciesReaderPluginServer_GetDependencies_Call) Run(run func(context1 context.Context, getDependenciesRequest *storage_v1.GetDependenciesRequest)) *DependenciesReaderPluginServer_GetDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.GetDependenciesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.GetDependenciesRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *DependenciesReaderPluginServer_GetDependencies_Call) Return(getDependenciesResponse *storage_v1.GetDependenciesResponse, err error) *DependenciesReaderPluginServer_GetDependencies_Call {\n\t_c.Call.Return(getDependenciesResponse, err)\n\treturn _c\n}\n\nfunc (_c *DependenciesReaderPluginServer_GetDependencies_Call) RunAndReturn(run func(context1 context.Context, getDependenciesRequest *storage_v1.GetDependenciesRequest) (*storage_v1.GetDependenciesResponse, error)) *DependenciesReaderPluginServer_GetDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewPluginCapabilitiesClient creates a new instance of PluginCapabilitiesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewPluginCapabilitiesClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *PluginCapabilitiesClient {\n\tmock := &PluginCapabilitiesClient{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// PluginCapabilitiesClient is an autogenerated mock type for the PluginCapabilitiesClient type\ntype PluginCapabilitiesClient struct {\n\tmock.Mock\n}\n\ntype PluginCapabilitiesClient_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *PluginCapabilitiesClient) EXPECT() *PluginCapabilitiesClient_Expecter {\n\treturn &PluginCapabilitiesClient_Expecter{mock: &_m.Mock}\n}\n\n// Capabilities provides a mock function for the type PluginCapabilitiesClient\nfunc (_mock *PluginCapabilitiesClient) Capabilities(ctx context.Context, in *storage_v1.CapabilitiesRequest, opts ...grpc.CallOption) (*storage_v1.CapabilitiesResponse, error) {\n\tvar tmpRet mock.Arguments\n\tif len(opts) > 0 {\n\t\ttmpRet = _mock.Called(ctx, in, opts)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx, in)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Capabilities\")\n\t}\n\n\tvar r0 *storage_v1.CapabilitiesResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CapabilitiesRequest, ...grpc.CallOption) (*storage_v1.CapabilitiesResponse, error)); ok {\n\t\treturn returnFunc(ctx, in, opts...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CapabilitiesRequest, ...grpc.CallOption) *storage_v1.CapabilitiesResponse); ok {\n\t\tr0 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.CapabilitiesResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.CapabilitiesRequest, ...grpc.CallOption) error); ok {\n\t\tr1 = returnFunc(ctx, in, opts...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// PluginCapabilitiesClient_Capabilities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Capabilities'\ntype PluginCapabilitiesClient_Capabilities_Call struct {\n\t*mock.Call\n}\n\n// Capabilities is a helper method to define mock.On call\n//   - ctx context.Context\n//   - in *storage_v1.CapabilitiesRequest\n//   - opts ...grpc.CallOption\nfunc (_e *PluginCapabilitiesClient_Expecter) Capabilities(ctx interface{}, in interface{}, opts ...interface{}) *PluginCapabilitiesClient_Capabilities_Call {\n\treturn &PluginCapabilitiesClient_Capabilities_Call{Call: _e.mock.On(\"Capabilities\",\n\t\tappend([]interface{}{ctx, in}, opts...)...)}\n}\n\nfunc (_c *PluginCapabilitiesClient_Capabilities_Call) Run(run func(ctx context.Context, in *storage_v1.CapabilitiesRequest, opts ...grpc.CallOption)) *PluginCapabilitiesClient_Capabilities_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.CapabilitiesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.CapabilitiesRequest)\n\t\t}\n\t\tvar arg2 []grpc.CallOption\n\t\tvar variadicArgs []grpc.CallOption\n\t\tif len(args) > 2 {\n\t\t\tvariadicArgs = args[2].([]grpc.CallOption)\n\t\t}\n\t\targ2 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *PluginCapabilitiesClient_Capabilities_Call) Return(capabilitiesResponse *storage_v1.CapabilitiesResponse, err error) *PluginCapabilitiesClient_Capabilities_Call {\n\t_c.Call.Return(capabilitiesResponse, err)\n\treturn _c\n}\n\nfunc (_c *PluginCapabilitiesClient_Capabilities_Call) RunAndReturn(run func(ctx context.Context, in *storage_v1.CapabilitiesRequest, opts ...grpc.CallOption) (*storage_v1.CapabilitiesResponse, error)) *PluginCapabilitiesClient_Capabilities_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewPluginCapabilitiesServer creates a new instance of PluginCapabilitiesServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewPluginCapabilitiesServer(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *PluginCapabilitiesServer {\n\tmock := &PluginCapabilitiesServer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// PluginCapabilitiesServer is an autogenerated mock type for the PluginCapabilitiesServer type\ntype PluginCapabilitiesServer struct {\n\tmock.Mock\n}\n\ntype PluginCapabilitiesServer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *PluginCapabilitiesServer) EXPECT() *PluginCapabilitiesServer_Expecter {\n\treturn &PluginCapabilitiesServer_Expecter{mock: &_m.Mock}\n}\n\n// Capabilities provides a mock function for the type PluginCapabilitiesServer\nfunc (_mock *PluginCapabilitiesServer) Capabilities(context1 context.Context, capabilitiesRequest *storage_v1.CapabilitiesRequest) (*storage_v1.CapabilitiesResponse, error) {\n\tret := _mock.Called(context1, capabilitiesRequest)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Capabilities\")\n\t}\n\n\tvar r0 *storage_v1.CapabilitiesResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CapabilitiesRequest) (*storage_v1.CapabilitiesResponse, error)); ok {\n\t\treturn returnFunc(context1, capabilitiesRequest)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *storage_v1.CapabilitiesRequest) *storage_v1.CapabilitiesResponse); ok {\n\t\tr0 = returnFunc(context1, capabilitiesRequest)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*storage_v1.CapabilitiesResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *storage_v1.CapabilitiesRequest) error); ok {\n\t\tr1 = returnFunc(context1, capabilitiesRequest)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// PluginCapabilitiesServer_Capabilities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Capabilities'\ntype PluginCapabilitiesServer_Capabilities_Call struct {\n\t*mock.Call\n}\n\n// Capabilities is a helper method to define mock.On call\n//   - context1 context.Context\n//   - capabilitiesRequest *storage_v1.CapabilitiesRequest\nfunc (_e *PluginCapabilitiesServer_Expecter) Capabilities(context1 interface{}, capabilitiesRequest interface{}) *PluginCapabilitiesServer_Capabilities_Call {\n\treturn &PluginCapabilitiesServer_Capabilities_Call{Call: _e.mock.On(\"Capabilities\", context1, capabilitiesRequest)}\n}\n\nfunc (_c *PluginCapabilitiesServer_Capabilities_Call) Run(run func(context1 context.Context, capabilitiesRequest *storage_v1.CapabilitiesRequest)) *PluginCapabilitiesServer_Capabilities_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *storage_v1.CapabilitiesRequest\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*storage_v1.CapabilitiesRequest)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *PluginCapabilitiesServer_Capabilities_Call) Return(capabilitiesResponse *storage_v1.CapabilitiesResponse, err error) *PluginCapabilitiesServer_Capabilities_Call {\n\t_c.Call.Return(capabilitiesResponse, err)\n\treturn _c\n}\n\nfunc (_c *PluginCapabilitiesServer_Capabilities_Call) RunAndReturn(run func(context1 context.Context, capabilitiesRequest *storage_v1.CapabilitiesRequest) (*storage_v1.CapabilitiesResponse, error)) *PluginCapabilitiesServer_Capabilities_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/proto-gen/storage_v1/storage.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: storage.proto\n\npackage storage_v1\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\t_ \"github.com/gogo/protobuf/gogoproto\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\t_ \"github.com/gogo/protobuf/types\"\n\tgithub_com_gogo_protobuf_types \"github.com/gogo/protobuf/types\"\n\tgithub_com_jaegertracing_jaeger_idl_model_v1 \"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tv1 \"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tio \"io\"\n\tmath \"math\"\n\tmath_bits \"math/bits\"\n\ttime \"time\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\nvar _ = time.Kitchen\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\ntype GetDependenciesRequest struct {\n\tStartTime            time.Time `protobuf:\"bytes,1,opt,name=start_time,json=startTime,proto3,stdtime\" json:\"start_time\"`\n\tEndTime              time.Time `protobuf:\"bytes,2,opt,name=end_time,json=endTime,proto3,stdtime\" json:\"end_time\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *GetDependenciesRequest) Reset()         { *m = GetDependenciesRequest{} }\nfunc (m *GetDependenciesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetDependenciesRequest) ProtoMessage()    {}\nfunc (*GetDependenciesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{0}\n}\nfunc (m *GetDependenciesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetDependenciesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetDependenciesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetDependenciesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetDependenciesRequest.Merge(m, src)\n}\nfunc (m *GetDependenciesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetDependenciesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetDependenciesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetDependenciesRequest proto.InternalMessageInfo\n\nfunc (m *GetDependenciesRequest) GetStartTime() time.Time {\n\tif m != nil {\n\t\treturn m.StartTime\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *GetDependenciesRequest) GetEndTime() time.Time {\n\tif m != nil {\n\t\treturn m.EndTime\n\t}\n\treturn time.Time{}\n}\n\ntype GetDependenciesResponse struct {\n\tDependencies         []v1.DependencyLink `protobuf:\"bytes,1,rep,name=dependencies,proto3\" json:\"dependencies\"`\n\tXXX_NoUnkeyedLiteral struct{}            `json:\"-\"`\n\tXXX_unrecognized     []byte              `json:\"-\"`\n\tXXX_sizecache        int32               `json:\"-\"`\n}\n\nfunc (m *GetDependenciesResponse) Reset()         { *m = GetDependenciesResponse{} }\nfunc (m *GetDependenciesResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetDependenciesResponse) ProtoMessage()    {}\nfunc (*GetDependenciesResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{1}\n}\nfunc (m *GetDependenciesResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetDependenciesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetDependenciesResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetDependenciesResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetDependenciesResponse.Merge(m, src)\n}\nfunc (m *GetDependenciesResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetDependenciesResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetDependenciesResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetDependenciesResponse proto.InternalMessageInfo\n\nfunc (m *GetDependenciesResponse) GetDependencies() []v1.DependencyLink {\n\tif m != nil {\n\t\treturn m.Dependencies\n\t}\n\treturn nil\n}\n\ntype WriteSpanRequest struct {\n\tSpan                 *v1.Span `protobuf:\"bytes,1,opt,name=span,proto3\" json:\"span,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *WriteSpanRequest) Reset()         { *m = WriteSpanRequest{} }\nfunc (m *WriteSpanRequest) String() string { return proto.CompactTextString(m) }\nfunc (*WriteSpanRequest) ProtoMessage()    {}\nfunc (*WriteSpanRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{2}\n}\nfunc (m *WriteSpanRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *WriteSpanRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_WriteSpanRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *WriteSpanRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_WriteSpanRequest.Merge(m, src)\n}\nfunc (m *WriteSpanRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *WriteSpanRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_WriteSpanRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_WriteSpanRequest proto.InternalMessageInfo\n\nfunc (m *WriteSpanRequest) GetSpan() *v1.Span {\n\tif m != nil {\n\t\treturn m.Span\n\t}\n\treturn nil\n}\n\n// empty; extensible in the future\ntype WriteSpanResponse struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *WriteSpanResponse) Reset()         { *m = WriteSpanResponse{} }\nfunc (m *WriteSpanResponse) String() string { return proto.CompactTextString(m) }\nfunc (*WriteSpanResponse) ProtoMessage()    {}\nfunc (*WriteSpanResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{3}\n}\nfunc (m *WriteSpanResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *WriteSpanResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_WriteSpanResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *WriteSpanResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_WriteSpanResponse.Merge(m, src)\n}\nfunc (m *WriteSpanResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *WriteSpanResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_WriteSpanResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_WriteSpanResponse proto.InternalMessageInfo\n\n// empty; extensible in the future\ntype CloseWriterRequest struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *CloseWriterRequest) Reset()         { *m = CloseWriterRequest{} }\nfunc (m *CloseWriterRequest) String() string { return proto.CompactTextString(m) }\nfunc (*CloseWriterRequest) ProtoMessage()    {}\nfunc (*CloseWriterRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{4}\n}\nfunc (m *CloseWriterRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *CloseWriterRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_CloseWriterRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *CloseWriterRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_CloseWriterRequest.Merge(m, src)\n}\nfunc (m *CloseWriterRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *CloseWriterRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_CloseWriterRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_CloseWriterRequest proto.InternalMessageInfo\n\n// empty; extensible in the future\ntype CloseWriterResponse struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *CloseWriterResponse) Reset()         { *m = CloseWriterResponse{} }\nfunc (m *CloseWriterResponse) String() string { return proto.CompactTextString(m) }\nfunc (*CloseWriterResponse) ProtoMessage()    {}\nfunc (*CloseWriterResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{5}\n}\nfunc (m *CloseWriterResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *CloseWriterResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_CloseWriterResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *CloseWriterResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_CloseWriterResponse.Merge(m, src)\n}\nfunc (m *CloseWriterResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *CloseWriterResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_CloseWriterResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_CloseWriterResponse proto.InternalMessageInfo\n\ntype GetTraceRequest struct {\n\tTraceID              github_com_jaegertracing_jaeger_idl_model_v1.TraceID `protobuf:\"bytes,1,opt,name=trace_id,json=traceId,proto3,customtype=github.com/jaegertracing/jaeger-idl/model/v1.TraceID\" json:\"trace_id\"`\n\tStartTime            time.Time                                            `protobuf:\"bytes,2,opt,name=start_time,json=startTime,proto3,stdtime\" json:\"start_time\"`\n\tEndTime              time.Time                                            `protobuf:\"bytes,3,opt,name=end_time,json=endTime,proto3,stdtime\" json:\"end_time\"`\n\tXXX_NoUnkeyedLiteral struct{}                                             `json:\"-\"`\n\tXXX_unrecognized     []byte                                               `json:\"-\"`\n\tXXX_sizecache        int32                                                `json:\"-\"`\n}\n\nfunc (m *GetTraceRequest) Reset()         { *m = GetTraceRequest{} }\nfunc (m *GetTraceRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetTraceRequest) ProtoMessage()    {}\nfunc (*GetTraceRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{6}\n}\nfunc (m *GetTraceRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetTraceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetTraceRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetTraceRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetTraceRequest.Merge(m, src)\n}\nfunc (m *GetTraceRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetTraceRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetTraceRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetTraceRequest proto.InternalMessageInfo\n\nfunc (m *GetTraceRequest) GetStartTime() time.Time {\n\tif m != nil {\n\t\treturn m.StartTime\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *GetTraceRequest) GetEndTime() time.Time {\n\tif m != nil {\n\t\treturn m.EndTime\n\t}\n\treturn time.Time{}\n}\n\ntype GetServicesRequest struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetServicesRequest) Reset()         { *m = GetServicesRequest{} }\nfunc (m *GetServicesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetServicesRequest) ProtoMessage()    {}\nfunc (*GetServicesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{7}\n}\nfunc (m *GetServicesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetServicesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetServicesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetServicesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetServicesRequest.Merge(m, src)\n}\nfunc (m *GetServicesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetServicesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetServicesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetServicesRequest proto.InternalMessageInfo\n\ntype GetServicesResponse struct {\n\tServices             []string `protobuf:\"bytes,1,rep,name=services,proto3\" json:\"services,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetServicesResponse) Reset()         { *m = GetServicesResponse{} }\nfunc (m *GetServicesResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetServicesResponse) ProtoMessage()    {}\nfunc (*GetServicesResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{8}\n}\nfunc (m *GetServicesResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetServicesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetServicesResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetServicesResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetServicesResponse.Merge(m, src)\n}\nfunc (m *GetServicesResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetServicesResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetServicesResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetServicesResponse proto.InternalMessageInfo\n\nfunc (m *GetServicesResponse) GetServices() []string {\n\tif m != nil {\n\t\treturn m.Services\n\t}\n\treturn nil\n}\n\ntype GetOperationsRequest struct {\n\tService              string   `protobuf:\"bytes,1,opt,name=service,proto3\" json:\"service,omitempty\"`\n\tSpanKind             string   `protobuf:\"bytes,2,opt,name=span_kind,json=spanKind,proto3\" json:\"span_kind,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *GetOperationsRequest) Reset()         { *m = GetOperationsRequest{} }\nfunc (m *GetOperationsRequest) String() string { return proto.CompactTextString(m) }\nfunc (*GetOperationsRequest) ProtoMessage()    {}\nfunc (*GetOperationsRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{9}\n}\nfunc (m *GetOperationsRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetOperationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetOperationsRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetOperationsRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetOperationsRequest.Merge(m, src)\n}\nfunc (m *GetOperationsRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetOperationsRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetOperationsRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetOperationsRequest proto.InternalMessageInfo\n\nfunc (m *GetOperationsRequest) GetService() string {\n\tif m != nil {\n\t\treturn m.Service\n\t}\n\treturn \"\"\n}\n\nfunc (m *GetOperationsRequest) GetSpanKind() string {\n\tif m != nil {\n\t\treturn m.SpanKind\n\t}\n\treturn \"\"\n}\n\ntype Operation struct {\n\tName                 string   `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tSpanKind             string   `protobuf:\"bytes,2,opt,name=span_kind,json=spanKind,proto3\" json:\"span_kind,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Operation) Reset()         { *m = Operation{} }\nfunc (m *Operation) String() string { return proto.CompactTextString(m) }\nfunc (*Operation) ProtoMessage()    {}\nfunc (*Operation) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{10}\n}\nfunc (m *Operation) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Operation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Operation.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Operation) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Operation.Merge(m, src)\n}\nfunc (m *Operation) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Operation) XXX_DiscardUnknown() {\n\txxx_messageInfo_Operation.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Operation proto.InternalMessageInfo\n\nfunc (m *Operation) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Operation) GetSpanKind() string {\n\tif m != nil {\n\t\treturn m.SpanKind\n\t}\n\treturn \"\"\n}\n\ntype GetOperationsResponse struct {\n\tOperationNames       []string     `protobuf:\"bytes,1,rep,name=operationNames,proto3\" json:\"operationNames,omitempty\"`\n\tOperations           []*Operation `protobuf:\"bytes,2,rep,name=operations,proto3\" json:\"operations,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}     `json:\"-\"`\n\tXXX_unrecognized     []byte       `json:\"-\"`\n\tXXX_sizecache        int32        `json:\"-\"`\n}\n\nfunc (m *GetOperationsResponse) Reset()         { *m = GetOperationsResponse{} }\nfunc (m *GetOperationsResponse) String() string { return proto.CompactTextString(m) }\nfunc (*GetOperationsResponse) ProtoMessage()    {}\nfunc (*GetOperationsResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{11}\n}\nfunc (m *GetOperationsResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *GetOperationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_GetOperationsResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *GetOperationsResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_GetOperationsResponse.Merge(m, src)\n}\nfunc (m *GetOperationsResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *GetOperationsResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_GetOperationsResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_GetOperationsResponse proto.InternalMessageInfo\n\nfunc (m *GetOperationsResponse) GetOperationNames() []string {\n\tif m != nil {\n\t\treturn m.OperationNames\n\t}\n\treturn nil\n}\n\nfunc (m *GetOperationsResponse) GetOperations() []*Operation {\n\tif m != nil {\n\t\treturn m.Operations\n\t}\n\treturn nil\n}\n\ntype TraceQueryParameters struct {\n\tServiceName          string            `protobuf:\"bytes,1,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\tOperationName        string            `protobuf:\"bytes,2,opt,name=operation_name,json=operationName,proto3\" json:\"operation_name,omitempty\"`\n\tTags                 map[string]string `protobuf:\"bytes,3,rep,name=tags,proto3\" json:\"tags,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\tStartTimeMin         time.Time         `protobuf:\"bytes,4,opt,name=start_time_min,json=startTimeMin,proto3,stdtime\" json:\"start_time_min\"`\n\tStartTimeMax         time.Time         `protobuf:\"bytes,5,opt,name=start_time_max,json=startTimeMax,proto3,stdtime\" json:\"start_time_max\"`\n\tDurationMin          time.Duration     `protobuf:\"bytes,6,opt,name=duration_min,json=durationMin,proto3,stdduration\" json:\"duration_min\"`\n\tDurationMax          time.Duration     `protobuf:\"bytes,7,opt,name=duration_max,json=durationMax,proto3,stdduration\" json:\"duration_max\"`\n\tNumTraces            int32             `protobuf:\"varint,8,opt,name=num_traces,json=numTraces,proto3\" json:\"num_traces,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}          `json:\"-\"`\n\tXXX_unrecognized     []byte            `json:\"-\"`\n\tXXX_sizecache        int32             `json:\"-\"`\n}\n\nfunc (m *TraceQueryParameters) Reset()         { *m = TraceQueryParameters{} }\nfunc (m *TraceQueryParameters) String() string { return proto.CompactTextString(m) }\nfunc (*TraceQueryParameters) ProtoMessage()    {}\nfunc (*TraceQueryParameters) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{12}\n}\nfunc (m *TraceQueryParameters) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *TraceQueryParameters) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_TraceQueryParameters.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *TraceQueryParameters) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_TraceQueryParameters.Merge(m, src)\n}\nfunc (m *TraceQueryParameters) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *TraceQueryParameters) XXX_DiscardUnknown() {\n\txxx_messageInfo_TraceQueryParameters.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_TraceQueryParameters proto.InternalMessageInfo\n\nfunc (m *TraceQueryParameters) GetServiceName() string {\n\tif m != nil {\n\t\treturn m.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (m *TraceQueryParameters) GetOperationName() string {\n\tif m != nil {\n\t\treturn m.OperationName\n\t}\n\treturn \"\"\n}\n\nfunc (m *TraceQueryParameters) GetTags() map[string]string {\n\tif m != nil {\n\t\treturn m.Tags\n\t}\n\treturn nil\n}\n\nfunc (m *TraceQueryParameters) GetStartTimeMin() time.Time {\n\tif m != nil {\n\t\treturn m.StartTimeMin\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *TraceQueryParameters) GetStartTimeMax() time.Time {\n\tif m != nil {\n\t\treturn m.StartTimeMax\n\t}\n\treturn time.Time{}\n}\n\nfunc (m *TraceQueryParameters) GetDurationMin() time.Duration {\n\tif m != nil {\n\t\treturn m.DurationMin\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetDurationMax() time.Duration {\n\tif m != nil {\n\t\treturn m.DurationMax\n\t}\n\treturn 0\n}\n\nfunc (m *TraceQueryParameters) GetNumTraces() int32 {\n\tif m != nil {\n\t\treturn m.NumTraces\n\t}\n\treturn 0\n}\n\ntype FindTracesRequest struct {\n\tQuery                *TraceQueryParameters `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}              `json:\"-\"`\n\tXXX_unrecognized     []byte                `json:\"-\"`\n\tXXX_sizecache        int32                 `json:\"-\"`\n}\n\nfunc (m *FindTracesRequest) Reset()         { *m = FindTracesRequest{} }\nfunc (m *FindTracesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*FindTracesRequest) ProtoMessage()    {}\nfunc (*FindTracesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{13}\n}\nfunc (m *FindTracesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FindTracesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FindTracesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FindTracesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FindTracesRequest.Merge(m, src)\n}\nfunc (m *FindTracesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FindTracesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_FindTracesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FindTracesRequest proto.InternalMessageInfo\n\nfunc (m *FindTracesRequest) GetQuery() *TraceQueryParameters {\n\tif m != nil {\n\t\treturn m.Query\n\t}\n\treturn nil\n}\n\ntype SpansResponseChunk struct {\n\tSpans                []v1.Span `protobuf:\"bytes,1,rep,name=spans,proto3\" json:\"spans\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *SpansResponseChunk) Reset()         { *m = SpansResponseChunk{} }\nfunc (m *SpansResponseChunk) String() string { return proto.CompactTextString(m) }\nfunc (*SpansResponseChunk) ProtoMessage()    {}\nfunc (*SpansResponseChunk) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{14}\n}\nfunc (m *SpansResponseChunk) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *SpansResponseChunk) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_SpansResponseChunk.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *SpansResponseChunk) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_SpansResponseChunk.Merge(m, src)\n}\nfunc (m *SpansResponseChunk) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *SpansResponseChunk) XXX_DiscardUnknown() {\n\txxx_messageInfo_SpansResponseChunk.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_SpansResponseChunk proto.InternalMessageInfo\n\nfunc (m *SpansResponseChunk) GetSpans() []v1.Span {\n\tif m != nil {\n\t\treturn m.Spans\n\t}\n\treturn nil\n}\n\ntype FindTraceIDsRequest struct {\n\tQuery                *TraceQueryParameters `protobuf:\"bytes,1,opt,name=query,proto3\" json:\"query,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}              `json:\"-\"`\n\tXXX_unrecognized     []byte                `json:\"-\"`\n\tXXX_sizecache        int32                 `json:\"-\"`\n}\n\nfunc (m *FindTraceIDsRequest) Reset()         { *m = FindTraceIDsRequest{} }\nfunc (m *FindTraceIDsRequest) String() string { return proto.CompactTextString(m) }\nfunc (*FindTraceIDsRequest) ProtoMessage()    {}\nfunc (*FindTraceIDsRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{15}\n}\nfunc (m *FindTraceIDsRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FindTraceIDsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FindTraceIDsRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FindTraceIDsRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FindTraceIDsRequest.Merge(m, src)\n}\nfunc (m *FindTraceIDsRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FindTraceIDsRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_FindTraceIDsRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FindTraceIDsRequest proto.InternalMessageInfo\n\nfunc (m *FindTraceIDsRequest) GetQuery() *TraceQueryParameters {\n\tif m != nil {\n\t\treturn m.Query\n\t}\n\treturn nil\n}\n\ntype FindTraceIDsResponse struct {\n\tTraceIDs             []github_com_jaegertracing_jaeger_idl_model_v1.TraceID `protobuf:\"bytes,1,rep,name=trace_ids,json=traceIds,proto3,customtype=github.com/jaegertracing/jaeger-idl/model/v1.TraceID\" json:\"trace_ids\"`\n\tXXX_NoUnkeyedLiteral struct{}                                               `json:\"-\"`\n\tXXX_unrecognized     []byte                                                 `json:\"-\"`\n\tXXX_sizecache        int32                                                  `json:\"-\"`\n}\n\nfunc (m *FindTraceIDsResponse) Reset()         { *m = FindTraceIDsResponse{} }\nfunc (m *FindTraceIDsResponse) String() string { return proto.CompactTextString(m) }\nfunc (*FindTraceIDsResponse) ProtoMessage()    {}\nfunc (*FindTraceIDsResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{16}\n}\nfunc (m *FindTraceIDsResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *FindTraceIDsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_FindTraceIDsResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *FindTraceIDsResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_FindTraceIDsResponse.Merge(m, src)\n}\nfunc (m *FindTraceIDsResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *FindTraceIDsResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_FindTraceIDsResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_FindTraceIDsResponse proto.InternalMessageInfo\n\n// empty; extensible in the future\ntype CapabilitiesRequest struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *CapabilitiesRequest) Reset()         { *m = CapabilitiesRequest{} }\nfunc (m *CapabilitiesRequest) String() string { return proto.CompactTextString(m) }\nfunc (*CapabilitiesRequest) ProtoMessage()    {}\nfunc (*CapabilitiesRequest) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{17}\n}\nfunc (m *CapabilitiesRequest) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *CapabilitiesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_CapabilitiesRequest.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *CapabilitiesRequest) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_CapabilitiesRequest.Merge(m, src)\n}\nfunc (m *CapabilitiesRequest) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *CapabilitiesRequest) XXX_DiscardUnknown() {\n\txxx_messageInfo_CapabilitiesRequest.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_CapabilitiesRequest proto.InternalMessageInfo\n\ntype CapabilitiesResponse struct {\n\tArchiveSpanReader    bool     `protobuf:\"varint,1,opt,name=archiveSpanReader,proto3\" json:\"archiveSpanReader,omitempty\"` // Deprecated: Do not use.\n\tArchiveSpanWriter    bool     `protobuf:\"varint,2,opt,name=archiveSpanWriter,proto3\" json:\"archiveSpanWriter,omitempty\"` // Deprecated: Do not use.\n\tStreamingSpanWriter  bool     `protobuf:\"varint,3,opt,name=streamingSpanWriter,proto3\" json:\"streamingSpanWriter,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *CapabilitiesResponse) Reset()         { *m = CapabilitiesResponse{} }\nfunc (m *CapabilitiesResponse) String() string { return proto.CompactTextString(m) }\nfunc (*CapabilitiesResponse) ProtoMessage()    {}\nfunc (*CapabilitiesResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_0d2c4ccf1453ffdb, []int{18}\n}\nfunc (m *CapabilitiesResponse) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *CapabilitiesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_CapabilitiesResponse.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *CapabilitiesResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_CapabilitiesResponse.Merge(m, src)\n}\nfunc (m *CapabilitiesResponse) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *CapabilitiesResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_CapabilitiesResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_CapabilitiesResponse proto.InternalMessageInfo\n\n// Deprecated: Do not use.\nfunc (m *CapabilitiesResponse) GetArchiveSpanReader() bool {\n\tif m != nil {\n\t\treturn m.ArchiveSpanReader\n\t}\n\treturn false\n}\n\n// Deprecated: Do not use.\nfunc (m *CapabilitiesResponse) GetArchiveSpanWriter() bool {\n\tif m != nil {\n\t\treturn m.ArchiveSpanWriter\n\t}\n\treturn false\n}\n\nfunc (m *CapabilitiesResponse) GetStreamingSpanWriter() bool {\n\tif m != nil {\n\t\treturn m.StreamingSpanWriter\n\t}\n\treturn false\n}\n\nfunc init() {\n\tproto.RegisterType((*GetDependenciesRequest)(nil), \"jaeger.storage.v1.GetDependenciesRequest\")\n\tproto.RegisterType((*GetDependenciesResponse)(nil), \"jaeger.storage.v1.GetDependenciesResponse\")\n\tproto.RegisterType((*WriteSpanRequest)(nil), \"jaeger.storage.v1.WriteSpanRequest\")\n\tproto.RegisterType((*WriteSpanResponse)(nil), \"jaeger.storage.v1.WriteSpanResponse\")\n\tproto.RegisterType((*CloseWriterRequest)(nil), \"jaeger.storage.v1.CloseWriterRequest\")\n\tproto.RegisterType((*CloseWriterResponse)(nil), \"jaeger.storage.v1.CloseWriterResponse\")\n\tproto.RegisterType((*GetTraceRequest)(nil), \"jaeger.storage.v1.GetTraceRequest\")\n\tproto.RegisterType((*GetServicesRequest)(nil), \"jaeger.storage.v1.GetServicesRequest\")\n\tproto.RegisterType((*GetServicesResponse)(nil), \"jaeger.storage.v1.GetServicesResponse\")\n\tproto.RegisterType((*GetOperationsRequest)(nil), \"jaeger.storage.v1.GetOperationsRequest\")\n\tproto.RegisterType((*Operation)(nil), \"jaeger.storage.v1.Operation\")\n\tproto.RegisterType((*GetOperationsResponse)(nil), \"jaeger.storage.v1.GetOperationsResponse\")\n\tproto.RegisterType((*TraceQueryParameters)(nil), \"jaeger.storage.v1.TraceQueryParameters\")\n\tproto.RegisterMapType((map[string]string)(nil), \"jaeger.storage.v1.TraceQueryParameters.TagsEntry\")\n\tproto.RegisterType((*FindTracesRequest)(nil), \"jaeger.storage.v1.FindTracesRequest\")\n\tproto.RegisterType((*SpansResponseChunk)(nil), \"jaeger.storage.v1.SpansResponseChunk\")\n\tproto.RegisterType((*FindTraceIDsRequest)(nil), \"jaeger.storage.v1.FindTraceIDsRequest\")\n\tproto.RegisterType((*FindTraceIDsResponse)(nil), \"jaeger.storage.v1.FindTraceIDsResponse\")\n\tproto.RegisterType((*CapabilitiesRequest)(nil), \"jaeger.storage.v1.CapabilitiesRequest\")\n\tproto.RegisterType((*CapabilitiesResponse)(nil), \"jaeger.storage.v1.CapabilitiesResponse\")\n}\n\nfunc init() { proto.RegisterFile(\"storage.proto\", fileDescriptor_0d2c4ccf1453ffdb) }\n\nvar fileDescriptor_0d2c4ccf1453ffdb = []byte{\n\t// 1142 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x73, 0xdb, 0x44,\n\t0x14, 0x47, 0x89, 0xdd, 0xd8, 0xcf, 0x4e, 0x9b, 0xac, 0x5d, 0xaa, 0x0a, 0x9a, 0x04, 0x41, 0x93,\n\t0xc0, 0x0c, 0x72, 0x62, 0x98, 0x81, 0x81, 0x32, 0x0c, 0x4e, 0x52, 0x13, 0xa0, 0x50, 0x94, 0x4c,\n\t0x3b, 0xc3, 0x9f, 0x7a, 0xd6, 0xd1, 0xa2, 0x2c, 0xb1, 0x56, 0xae, 0xfe, 0x78, 0x92, 0x61, 0x7a,\n\t0xe3, 0x03, 0x70, 0xe4, 0xc4, 0x95, 0x0b, 0x9f, 0x82, 0x53, 0x8f, 0x9c, 0x39, 0x04, 0x26, 0x57,\n\t0x3e, 0x02, 0x97, 0x8e, 0x76, 0x57, 0xb2, 0x64, 0x69, 0x92, 0x34, 0xcd, 0xcd, 0xfb, 0xf6, 0xf7,\n\t0x7e, 0xef, 0xef, 0xbe, 0x27, 0xc3, 0xac, 0x1f, 0xb8, 0x1e, 0xb6, 0x89, 0x31, 0xf4, 0xdc, 0xc0,\n\t0x45, 0xf3, 0x3f, 0x62, 0x62, 0x13, 0xcf, 0x88, 0xa5, 0xa3, 0x75, 0xad, 0x69, 0xbb, 0xb6, 0xcb,\n\t0x6f, 0x5b, 0xd1, 0x2f, 0x01, 0xd4, 0x16, 0x6d, 0xd7, 0xb5, 0x07, 0xa4, 0xc5, 0x4f, 0xfd, 0xf0,\n\t0x87, 0x56, 0x40, 0x1d, 0xe2, 0x07, 0xd8, 0x19, 0x4a, 0xc0, 0xc2, 0x24, 0xc0, 0x0a, 0x3d, 0x1c,\n\t0x50, 0x97, 0xc9, 0xfb, 0x9a, 0xe3, 0x5a, 0x64, 0x20, 0x0e, 0xfa, 0x6f, 0x0a, 0xbc, 0xdc, 0x25,\n\t0xc1, 0x26, 0x19, 0x12, 0x66, 0x11, 0xb6, 0x47, 0x89, 0x6f, 0x92, 0xc7, 0x21, 0xf1, 0x03, 0xb4,\n\t0x01, 0xe0, 0x07, 0xd8, 0x0b, 0x7a, 0x91, 0x01, 0x55, 0x59, 0x52, 0x56, 0x6b, 0x6d, 0xcd, 0x10,\n\t0xe4, 0x46, 0x4c, 0x6e, 0xec, 0xc6, 0xd6, 0x3b, 0x95, 0xa7, 0xc7, 0x8b, 0x2f, 0xfd, 0xf2, 0xcf,\n\t0xa2, 0x62, 0x56, 0xb9, 0x5e, 0x74, 0x83, 0x3e, 0x86, 0x0a, 0x61, 0x96, 0xa0, 0x98, 0x7a, 0x0e,\n\t0x8a, 0x19, 0xc2, 0xac, 0x48, 0xae, 0xf7, 0xe1, 0x46, 0xce, 0x3f, 0x7f, 0xe8, 0x32, 0x9f, 0xa0,\n\t0x2e, 0xd4, 0xad, 0x94, 0x5c, 0x55, 0x96, 0xa6, 0x57, 0x6b, 0xed, 0x5b, 0x86, 0xcc, 0x24, 0x1e,\n\t0xd2, 0xde, 0xa8, 0x6d, 0x24, 0xaa, 0x47, 0x5f, 0x50, 0x76, 0xd0, 0x29, 0x45, 0x26, 0xcc, 0x8c,\n\t0xa2, 0xfe, 0x21, 0xcc, 0x3d, 0xf4, 0x68, 0x40, 0x76, 0x86, 0x98, 0xc5, 0xd1, 0xaf, 0x40, 0xc9,\n\t0x1f, 0x62, 0x26, 0xe3, 0x6e, 0x4c, 0x90, 0x72, 0x24, 0x07, 0xe8, 0x0d, 0x98, 0x4f, 0x29, 0x0b,\n\t0xd7, 0xf4, 0x26, 0xa0, 0x8d, 0x81, 0xeb, 0x13, 0x7e, 0xe3, 0x49, 0x4e, 0xfd, 0x3a, 0x34, 0x32,\n\t0x52, 0x09, 0xfe, 0x5f, 0x81, 0x6b, 0x5d, 0x12, 0xec, 0x7a, 0x78, 0x8f, 0xc4, 0xe6, 0xfb, 0x50,\n\t0x09, 0xa2, 0x73, 0x8f, 0x5a, 0xdc, 0x85, 0x7a, 0xa7, 0x1b, 0x39, 0xfe, 0xf7, 0xf1, 0xe2, 0xbb,\n\t0x36, 0x0d, 0xf6, 0xc3, 0xbe, 0xb1, 0xe7, 0x3a, 0x2d, 0xe1, 0x54, 0x04, 0xa4, 0xcc, 0x96, 0xa7,\n\t0xb7, 0xa9, 0x35, 0x68, 0xf1, 0x12, 0xb7, 0x46, 0xeb, 0x06, 0x27, 0xdd, 0xde, 0x3c, 0x39, 0x5e,\n\t0x9c, 0x91, 0x3f, 0xcd, 0x19, 0x4e, 0xbc, 0x6d, 0x4d, 0x14, 0x78, 0xea, 0xc5, 0x0b, 0x3c, 0x7d,\n\t0x91, 0x02, 0x37, 0x01, 0x75, 0x49, 0xb0, 0x43, 0xbc, 0x11, 0xdd, 0x4b, 0x9a, 0x4f, 0x5f, 0x87,\n\t0x46, 0x46, 0x2a, 0x4b, 0xae, 0x41, 0xc5, 0x97, 0x32, 0x5e, 0xee, 0xaa, 0x99, 0x9c, 0xf5, 0x7b,\n\t0xd0, 0xec, 0x92, 0xe0, 0xab, 0x21, 0x11, 0xdd, 0x9e, 0xf4, 0xb1, 0x0a, 0x33, 0x12, 0xc3, 0x33,\n\t0x59, 0x35, 0xe3, 0x23, 0x7a, 0x05, 0xaa, 0x51, 0x09, 0x7b, 0x07, 0x94, 0x59, 0x3c, 0xfe, 0x88,\n\t0x6e, 0x88, 0xd9, 0xe7, 0x94, 0x59, 0xfa, 0x1d, 0xa8, 0x26, 0x5c, 0x08, 0x41, 0x89, 0x61, 0x27,\n\t0x26, 0xe0, 0xbf, 0x4f, 0xd7, 0x7e, 0x02, 0xd7, 0x27, 0x9c, 0x91, 0x11, 0x2c, 0xc3, 0x55, 0x37,\n\t0x96, 0x7e, 0x89, 0x9d, 0x24, 0x8e, 0x09, 0x29, 0xba, 0x03, 0x90, 0x48, 0x7c, 0x75, 0x8a, 0xb7,\n\t0xf6, 0xab, 0x46, 0x6e, 0x48, 0x18, 0x89, 0x09, 0x33, 0x85, 0xd7, 0x7f, 0x2f, 0x41, 0x93, 0xd7,\n\t0xfb, 0xeb, 0x90, 0x78, 0x47, 0xf7, 0xb1, 0x87, 0x1d, 0x12, 0x10, 0xcf, 0x47, 0xaf, 0x41, 0x5d,\n\t0x46, 0xdf, 0x4b, 0x05, 0x54, 0x93, 0xb2, 0xc8, 0x34, 0xba, 0x9d, 0xf2, 0x50, 0x80, 0x44, 0x70,\n\t0xb3, 0x19, 0x0f, 0xd1, 0x16, 0x94, 0x02, 0x6c, 0xfb, 0xea, 0x34, 0x77, 0x6d, 0xbd, 0xc0, 0xb5,\n\t0x22, 0x07, 0x8c, 0x5d, 0x6c, 0xfb, 0x5b, 0x2c, 0xf0, 0x8e, 0x4c, 0xae, 0x8e, 0x3e, 0x83, 0xab,\n\t0xe3, 0x26, 0xec, 0x39, 0x94, 0xa9, 0xa5, 0xe7, 0xe8, 0xa2, 0x7a, 0xd2, 0x88, 0xf7, 0x28, 0x9b,\n\t0xe4, 0xc2, 0x87, 0x6a, 0xf9, 0x62, 0x5c, 0xf8, 0x10, 0xdd, 0x85, 0x7a, 0x3c, 0x37, 0xb9, 0x57,\n\t0x57, 0x38, 0xd3, 0xcd, 0x1c, 0xd3, 0xa6, 0x04, 0x09, 0xa2, 0x5f, 0x23, 0xa2, 0x5a, 0xac, 0x18,\n\t0xf9, 0x94, 0xe1, 0xc1, 0x87, 0xea, 0xcc, 0x45, 0x78, 0xf0, 0x21, 0xba, 0x05, 0xc0, 0x42, 0xa7,\n\t0xc7, 0xdf, 0xae, 0xaf, 0x56, 0x96, 0x94, 0xd5, 0xb2, 0x59, 0x65, 0xa1, 0xc3, 0x93, 0xec, 0x6b,\n\t0xef, 0x41, 0x35, 0xc9, 0x2c, 0x9a, 0x83, 0xe9, 0x03, 0x72, 0x24, 0x6b, 0x1b, 0xfd, 0x44, 0x4d,\n\t0x28, 0x8f, 0xf0, 0x20, 0x8c, 0x4b, 0x29, 0x0e, 0x1f, 0x4c, 0xbd, 0xaf, 0xe8, 0x26, 0xcc, 0xdf,\n\t0xa5, 0xcc, 0x12, 0x34, 0xf1, 0x93, 0xf9, 0x08, 0xca, 0x8f, 0xa3, 0xba, 0xc9, 0xe9, 0xb7, 0x72,\n\t0xce, 0xe2, 0x9a, 0x42, 0x4b, 0xdf, 0x02, 0x14, 0x4d, 0xc3, 0xa4, 0xe9, 0x37, 0xf6, 0x43, 0x76,\n\t0x80, 0x5a, 0x50, 0x8e, 0x9e, 0x47, 0x3c, 0xa7, 0x8b, 0x46, 0xaa, 0x9c, 0xce, 0x02, 0xa7, 0xef,\n\t0x42, 0x23, 0x71, 0x6d, 0x7b, 0xf3, 0xb2, 0x9c, 0x7b, 0x02, 0xcd, 0x2c, 0xab, 0x7c, 0x98, 0x04,\n\t0xaa, 0xf1, 0xc4, 0x15, 0x2e, 0xd6, 0x3b, 0x9f, 0xbe, 0xe0, 0xc8, 0xad, 0x24, 0x46, 0x2a, 0x72,\n\t0xe6, 0xfa, 0x7c, 0x07, 0xe0, 0x21, 0xee, 0xd3, 0x01, 0x0d, 0xc6, 0xcb, 0x56, 0xff, 0x43, 0x81,\n\t0x66, 0x56, 0x2e, 0xdd, 0x5a, 0x83, 0x79, 0xec, 0xed, 0xed, 0xd3, 0x91, 0x5c, 0x30, 0xd8, 0x22,\n\t0x1e, 0x8f, 0xbc, 0xd2, 0x99, 0x52, 0x15, 0x33, 0x7f, 0x39, 0xa1, 0x21, 0x76, 0x0d, 0xaf, 0x7b,\n\t0x5e, 0x43, 0x5c, 0xa2, 0x35, 0x68, 0xf8, 0x81, 0x47, 0xb0, 0x43, 0x99, 0x9d, 0xd2, 0x89, 0xc6,\n\t0x79, 0xc5, 0x2c, 0xba, 0x6a, 0xff, 0xa9, 0xc0, 0xdc, 0xf8, 0x78, 0x7f, 0x10, 0xda, 0x94, 0xa1,\n\t0x07, 0x50, 0x4d, 0x36, 0x21, 0x7a, 0xbd, 0xa0, 0x2c, 0x93, 0x4b, 0x56, 0x7b, 0xe3, 0x74, 0x90,\n\t0x4c, 0xc1, 0x03, 0x28, 0xf3, 0xb5, 0x89, 0x6e, 0x17, 0xc0, 0xf3, 0x6b, 0x56, 0x5b, 0x3e, 0x0b,\n\t0x26, 0x78, 0xdb, 0x3f, 0xc1, 0xcd, 0x9d, 0x7c, 0x6c, 0x32, 0x98, 0x47, 0x70, 0x2d, 0xf1, 0x44,\n\t0xa0, 0x2e, 0x31, 0xa4, 0x55, 0xa5, 0xfd, 0xdf, 0xb4, 0xc8, 0xa0, 0x28, 0x9a, 0x34, 0xfa, 0x10,\n\t0x2a, 0xf1, 0x87, 0x00, 0xd2, 0x0b, 0x88, 0x26, 0xbe, 0x12, 0xb4, 0xa2, 0x84, 0xe4, 0x5f, 0xde,\n\t0x9a, 0x82, 0xbe, 0x83, 0x5a, 0x6a, 0x9d, 0x16, 0x26, 0x32, 0xbf, 0x84, 0x0b, 0x13, 0x59, 0xb4,\n\t0x95, 0xfb, 0x30, 0x9b, 0x59, 0x76, 0x68, 0xa5, 0x58, 0x31, 0xb7, 0x9b, 0xb5, 0xd5, 0xb3, 0x81,\n\t0xd2, 0xc6, 0xb7, 0x00, 0xe3, 0x39, 0x85, 0x8a, 0xb2, 0x9c, 0x1b, 0x63, 0xe7, 0x4f, 0x4f, 0x0f,\n\t0xea, 0xe9, 0x99, 0x80, 0x96, 0x4f, 0xa3, 0x1f, 0x8f, 0x22, 0x6d, 0xe5, 0x4c, 0x9c, 0x6c, 0xb5,\n\t0x43, 0xb8, 0xf1, 0xc9, 0xe4, 0xb3, 0x93, 0x35, 0xff, 0x5e, 0x7e, 0x7c, 0xa6, 0xee, 0x2f, 0xb1,\n\t0xd3, 0xda, 0x47, 0x19, 0xcb, 0x99, 0x6e, 0x7b, 0xc4, 0x3f, 0x3b, 0xe5, 0xed, 0xe5, 0x37, 0x5d,\n\t0xfb, 0x67, 0x05, 0xd4, 0xec, 0x87, 0x7b, 0xca, 0xf8, 0x3e, 0x37, 0x9e, 0xbe, 0x46, 0x6f, 0x16,\n\t0x1b, 0x2f, 0xf8, 0x6f, 0xa2, 0xbd, 0x75, 0x1e, 0xa8, 0xcc, 0x40, 0x08, 0x48, 0xd8, 0x4c, 0xcf,\n\t0xd7, 0xa8, 0xe4, 0x99, 0x73, 0xe1, 0xd0, 0xc8, 0x0f, 0xea, 0xc2, 0x92, 0x17, 0x0d, 0xee, 0x8e,\n\t0xfa, 0xf4, 0x64, 0x41, 0xf9, 0xeb, 0x64, 0x41, 0xf9, 0xf7, 0x64, 0x41, 0xf9, 0x06, 0x24, 0xbc,\n\t0x37, 0x5a, 0xef, 0x5f, 0xe1, 0x4b, 0xff, 0x9d, 0x67, 0x01, 0x00, 0x00, 0xff, 0xff, 0x6c, 0xd4,\n\t0x66, 0xf9, 0x02, 0x0e, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// SpanWriterPluginClient is the client API for SpanWriterPlugin service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype SpanWriterPluginClient interface {\n\t// spanstore/Writer\n\tWriteSpan(ctx context.Context, in *WriteSpanRequest, opts ...grpc.CallOption) (*WriteSpanResponse, error)\n\tClose(ctx context.Context, in *CloseWriterRequest, opts ...grpc.CallOption) (*CloseWriterResponse, error)\n}\n\ntype spanWriterPluginClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewSpanWriterPluginClient(cc *grpc.ClientConn) SpanWriterPluginClient {\n\treturn &spanWriterPluginClient{cc}\n}\n\nfunc (c *spanWriterPluginClient) WriteSpan(ctx context.Context, in *WriteSpanRequest, opts ...grpc.CallOption) (*WriteSpanResponse, error) {\n\tout := new(WriteSpanResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.SpanWriterPlugin/WriteSpan\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *spanWriterPluginClient) Close(ctx context.Context, in *CloseWriterRequest, opts ...grpc.CallOption) (*CloseWriterResponse, error) {\n\tout := new(CloseWriterResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.SpanWriterPlugin/Close\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// SpanWriterPluginServer is the server API for SpanWriterPlugin service.\ntype SpanWriterPluginServer interface {\n\t// spanstore/Writer\n\tWriteSpan(context.Context, *WriteSpanRequest) (*WriteSpanResponse, error)\n\tClose(context.Context, *CloseWriterRequest) (*CloseWriterResponse, error)\n}\n\n// UnimplementedSpanWriterPluginServer can be embedded to have forward compatible implementations.\ntype UnimplementedSpanWriterPluginServer struct {\n}\n\nfunc (*UnimplementedSpanWriterPluginServer) WriteSpan(ctx context.Context, req *WriteSpanRequest) (*WriteSpanResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method WriteSpan not implemented\")\n}\nfunc (*UnimplementedSpanWriterPluginServer) Close(ctx context.Context, req *CloseWriterRequest) (*CloseWriterResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Close not implemented\")\n}\n\nfunc RegisterSpanWriterPluginServer(s *grpc.Server, srv SpanWriterPluginServer) {\n\ts.RegisterService(&_SpanWriterPlugin_serviceDesc, srv)\n}\n\nfunc _SpanWriterPlugin_WriteSpan_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(WriteSpanRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(SpanWriterPluginServer).WriteSpan(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.SpanWriterPlugin/WriteSpan\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(SpanWriterPluginServer).WriteSpan(ctx, req.(*WriteSpanRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _SpanWriterPlugin_Close_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CloseWriterRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(SpanWriterPluginServer).Close(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.SpanWriterPlugin/Close\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(SpanWriterPluginServer).Close(ctx, req.(*CloseWriterRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _SpanWriterPlugin_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.SpanWriterPlugin\",\n\tHandlerType: (*SpanWriterPluginServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"WriteSpan\",\n\t\t\tHandler:    _SpanWriterPlugin_WriteSpan_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Close\",\n\t\t\tHandler:    _SpanWriterPlugin_Close_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"storage.proto\",\n}\n\n// StreamingSpanWriterPluginClient is the client API for StreamingSpanWriterPlugin service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype StreamingSpanWriterPluginClient interface {\n\tWriteSpanStream(ctx context.Context, opts ...grpc.CallOption) (StreamingSpanWriterPlugin_WriteSpanStreamClient, error)\n}\n\ntype streamingSpanWriterPluginClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewStreamingSpanWriterPluginClient(cc *grpc.ClientConn) StreamingSpanWriterPluginClient {\n\treturn &streamingSpanWriterPluginClient{cc}\n}\n\nfunc (c *streamingSpanWriterPluginClient) WriteSpanStream(ctx context.Context, opts ...grpc.CallOption) (StreamingSpanWriterPlugin_WriteSpanStreamClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_StreamingSpanWriterPlugin_serviceDesc.Streams[0], \"/jaeger.storage.v1.StreamingSpanWriterPlugin/WriteSpanStream\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &streamingSpanWriterPluginWriteSpanStreamClient{stream}\n\treturn x, nil\n}\n\ntype StreamingSpanWriterPlugin_WriteSpanStreamClient interface {\n\tSend(*WriteSpanRequest) error\n\tCloseAndRecv() (*WriteSpanResponse, error)\n\tgrpc.ClientStream\n}\n\ntype streamingSpanWriterPluginWriteSpanStreamClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *streamingSpanWriterPluginWriteSpanStreamClient) Send(m *WriteSpanRequest) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *streamingSpanWriterPluginWriteSpanStreamClient) CloseAndRecv() (*WriteSpanResponse, error) {\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := new(WriteSpanResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// StreamingSpanWriterPluginServer is the server API for StreamingSpanWriterPlugin service.\ntype StreamingSpanWriterPluginServer interface {\n\tWriteSpanStream(StreamingSpanWriterPlugin_WriteSpanStreamServer) error\n}\n\n// UnimplementedStreamingSpanWriterPluginServer can be embedded to have forward compatible implementations.\ntype UnimplementedStreamingSpanWriterPluginServer struct {\n}\n\nfunc (*UnimplementedStreamingSpanWriterPluginServer) WriteSpanStream(srv StreamingSpanWriterPlugin_WriteSpanStreamServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method WriteSpanStream not implemented\")\n}\n\nfunc RegisterStreamingSpanWriterPluginServer(s *grpc.Server, srv StreamingSpanWriterPluginServer) {\n\ts.RegisterService(&_StreamingSpanWriterPlugin_serviceDesc, srv)\n}\n\nfunc _StreamingSpanWriterPlugin_WriteSpanStream_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(StreamingSpanWriterPluginServer).WriteSpanStream(&streamingSpanWriterPluginWriteSpanStreamServer{stream})\n}\n\ntype StreamingSpanWriterPlugin_WriteSpanStreamServer interface {\n\tSendAndClose(*WriteSpanResponse) error\n\tRecv() (*WriteSpanRequest, error)\n\tgrpc.ServerStream\n}\n\ntype streamingSpanWriterPluginWriteSpanStreamServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *streamingSpanWriterPluginWriteSpanStreamServer) SendAndClose(m *WriteSpanResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *streamingSpanWriterPluginWriteSpanStreamServer) Recv() (*WriteSpanRequest, error) {\n\tm := new(WriteSpanRequest)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nvar _StreamingSpanWriterPlugin_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.StreamingSpanWriterPlugin\",\n\tHandlerType: (*StreamingSpanWriterPluginServer)(nil),\n\tMethods:     []grpc.MethodDesc{},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"WriteSpanStream\",\n\t\t\tHandler:       _StreamingSpanWriterPlugin_WriteSpanStream_Handler,\n\t\t\tClientStreams: true,\n\t\t},\n\t},\n\tMetadata: \"storage.proto\",\n}\n\n// SpanReaderPluginClient is the client API for SpanReaderPlugin service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype SpanReaderPluginClient interface {\n\t// spanstore/Reader\n\tGetTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (SpanReaderPlugin_GetTraceClient, error)\n\tGetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error)\n\tGetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error)\n\tFindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (SpanReaderPlugin_FindTracesClient, error)\n\tFindTraceIDs(ctx context.Context, in *FindTraceIDsRequest, opts ...grpc.CallOption) (*FindTraceIDsResponse, error)\n}\n\ntype spanReaderPluginClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewSpanReaderPluginClient(cc *grpc.ClientConn) SpanReaderPluginClient {\n\treturn &spanReaderPluginClient{cc}\n}\n\nfunc (c *spanReaderPluginClient) GetTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (SpanReaderPlugin_GetTraceClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_SpanReaderPlugin_serviceDesc.Streams[0], \"/jaeger.storage.v1.SpanReaderPlugin/GetTrace\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &spanReaderPluginGetTraceClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype SpanReaderPlugin_GetTraceClient interface {\n\tRecv() (*SpansResponseChunk, error)\n\tgrpc.ClientStream\n}\n\ntype spanReaderPluginGetTraceClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *spanReaderPluginGetTraceClient) Recv() (*SpansResponseChunk, error) {\n\tm := new(SpansResponseChunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *spanReaderPluginClient) GetServices(ctx context.Context, in *GetServicesRequest, opts ...grpc.CallOption) (*GetServicesResponse, error) {\n\tout := new(GetServicesResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.SpanReaderPlugin/GetServices\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *spanReaderPluginClient) GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*GetOperationsResponse, error) {\n\tout := new(GetOperationsResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.SpanReaderPlugin/GetOperations\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *spanReaderPluginClient) FindTraces(ctx context.Context, in *FindTracesRequest, opts ...grpc.CallOption) (SpanReaderPlugin_FindTracesClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_SpanReaderPlugin_serviceDesc.Streams[1], \"/jaeger.storage.v1.SpanReaderPlugin/FindTraces\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &spanReaderPluginFindTracesClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype SpanReaderPlugin_FindTracesClient interface {\n\tRecv() (*SpansResponseChunk, error)\n\tgrpc.ClientStream\n}\n\ntype spanReaderPluginFindTracesClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *spanReaderPluginFindTracesClient) Recv() (*SpansResponseChunk, error) {\n\tm := new(SpansResponseChunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *spanReaderPluginClient) FindTraceIDs(ctx context.Context, in *FindTraceIDsRequest, opts ...grpc.CallOption) (*FindTraceIDsResponse, error) {\n\tout := new(FindTraceIDsResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.SpanReaderPlugin/FindTraceIDs\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// SpanReaderPluginServer is the server API for SpanReaderPlugin service.\ntype SpanReaderPluginServer interface {\n\t// spanstore/Reader\n\tGetTrace(*GetTraceRequest, SpanReaderPlugin_GetTraceServer) error\n\tGetServices(context.Context, *GetServicesRequest) (*GetServicesResponse, error)\n\tGetOperations(context.Context, *GetOperationsRequest) (*GetOperationsResponse, error)\n\tFindTraces(*FindTracesRequest, SpanReaderPlugin_FindTracesServer) error\n\tFindTraceIDs(context.Context, *FindTraceIDsRequest) (*FindTraceIDsResponse, error)\n}\n\n// UnimplementedSpanReaderPluginServer can be embedded to have forward compatible implementations.\ntype UnimplementedSpanReaderPluginServer struct {\n}\n\nfunc (*UnimplementedSpanReaderPluginServer) GetTrace(req *GetTraceRequest, srv SpanReaderPlugin_GetTraceServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method GetTrace not implemented\")\n}\nfunc (*UnimplementedSpanReaderPluginServer) GetServices(ctx context.Context, req *GetServicesRequest) (*GetServicesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetServices not implemented\")\n}\nfunc (*UnimplementedSpanReaderPluginServer) GetOperations(ctx context.Context, req *GetOperationsRequest) (*GetOperationsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetOperations not implemented\")\n}\nfunc (*UnimplementedSpanReaderPluginServer) FindTraces(req *FindTracesRequest, srv SpanReaderPlugin_FindTracesServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method FindTraces not implemented\")\n}\nfunc (*UnimplementedSpanReaderPluginServer) FindTraceIDs(ctx context.Context, req *FindTraceIDsRequest) (*FindTraceIDsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method FindTraceIDs not implemented\")\n}\n\nfunc RegisterSpanReaderPluginServer(s *grpc.Server, srv SpanReaderPluginServer) {\n\ts.RegisterService(&_SpanReaderPlugin_serviceDesc, srv)\n}\n\nfunc _SpanReaderPlugin_GetTrace_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(GetTraceRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(SpanReaderPluginServer).GetTrace(m, &spanReaderPluginGetTraceServer{stream})\n}\n\ntype SpanReaderPlugin_GetTraceServer interface {\n\tSend(*SpansResponseChunk) error\n\tgrpc.ServerStream\n}\n\ntype spanReaderPluginGetTraceServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *spanReaderPluginGetTraceServer) Send(m *SpansResponseChunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _SpanReaderPlugin_GetServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetServicesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(SpanReaderPluginServer).GetServices(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.SpanReaderPlugin/GetServices\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(SpanReaderPluginServer).GetServices(ctx, req.(*GetServicesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _SpanReaderPlugin_GetOperations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetOperationsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(SpanReaderPluginServer).GetOperations(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.SpanReaderPlugin/GetOperations\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(SpanReaderPluginServer).GetOperations(ctx, req.(*GetOperationsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _SpanReaderPlugin_FindTraces_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(FindTracesRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(SpanReaderPluginServer).FindTraces(m, &spanReaderPluginFindTracesServer{stream})\n}\n\ntype SpanReaderPlugin_FindTracesServer interface {\n\tSend(*SpansResponseChunk) error\n\tgrpc.ServerStream\n}\n\ntype spanReaderPluginFindTracesServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *spanReaderPluginFindTracesServer) Send(m *SpansResponseChunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _SpanReaderPlugin_FindTraceIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(FindTraceIDsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(SpanReaderPluginServer).FindTraceIDs(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.SpanReaderPlugin/FindTraceIDs\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(SpanReaderPluginServer).FindTraceIDs(ctx, req.(*FindTraceIDsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _SpanReaderPlugin_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.SpanReaderPlugin\",\n\tHandlerType: (*SpanReaderPluginServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetServices\",\n\t\t\tHandler:    _SpanReaderPlugin_GetServices_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetOperations\",\n\t\t\tHandler:    _SpanReaderPlugin_GetOperations_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"FindTraceIDs\",\n\t\t\tHandler:    _SpanReaderPlugin_FindTraceIDs_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"GetTrace\",\n\t\t\tHandler:       _SpanReaderPlugin_GetTrace_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"FindTraces\",\n\t\t\tHandler:       _SpanReaderPlugin_FindTraces_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"storage.proto\",\n}\n\n// ArchiveSpanWriterPluginClient is the client API for ArchiveSpanWriterPlugin service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype ArchiveSpanWriterPluginClient interface {\n\t// spanstore/Writer\n\tWriteArchiveSpan(ctx context.Context, in *WriteSpanRequest, opts ...grpc.CallOption) (*WriteSpanResponse, error)\n}\n\ntype archiveSpanWriterPluginClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewArchiveSpanWriterPluginClient(cc *grpc.ClientConn) ArchiveSpanWriterPluginClient {\n\treturn &archiveSpanWriterPluginClient{cc}\n}\n\nfunc (c *archiveSpanWriterPluginClient) WriteArchiveSpan(ctx context.Context, in *WriteSpanRequest, opts ...grpc.CallOption) (*WriteSpanResponse, error) {\n\tout := new(WriteSpanResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.ArchiveSpanWriterPlugin/WriteArchiveSpan\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ArchiveSpanWriterPluginServer is the server API for ArchiveSpanWriterPlugin service.\ntype ArchiveSpanWriterPluginServer interface {\n\t// spanstore/Writer\n\tWriteArchiveSpan(context.Context, *WriteSpanRequest) (*WriteSpanResponse, error)\n}\n\n// UnimplementedArchiveSpanWriterPluginServer can be embedded to have forward compatible implementations.\ntype UnimplementedArchiveSpanWriterPluginServer struct {\n}\n\nfunc (*UnimplementedArchiveSpanWriterPluginServer) WriteArchiveSpan(ctx context.Context, req *WriteSpanRequest) (*WriteSpanResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method WriteArchiveSpan not implemented\")\n}\n\nfunc RegisterArchiveSpanWriterPluginServer(s *grpc.Server, srv ArchiveSpanWriterPluginServer) {\n\ts.RegisterService(&_ArchiveSpanWriterPlugin_serviceDesc, srv)\n}\n\nfunc _ArchiveSpanWriterPlugin_WriteArchiveSpan_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(WriteSpanRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ArchiveSpanWriterPluginServer).WriteArchiveSpan(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.ArchiveSpanWriterPlugin/WriteArchiveSpan\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ArchiveSpanWriterPluginServer).WriteArchiveSpan(ctx, req.(*WriteSpanRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _ArchiveSpanWriterPlugin_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.ArchiveSpanWriterPlugin\",\n\tHandlerType: (*ArchiveSpanWriterPluginServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"WriteArchiveSpan\",\n\t\t\tHandler:    _ArchiveSpanWriterPlugin_WriteArchiveSpan_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"storage.proto\",\n}\n\n// ArchiveSpanReaderPluginClient is the client API for ArchiveSpanReaderPlugin service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype ArchiveSpanReaderPluginClient interface {\n\t// spanstore/Reader\n\tGetArchiveTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (ArchiveSpanReaderPlugin_GetArchiveTraceClient, error)\n}\n\ntype archiveSpanReaderPluginClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewArchiveSpanReaderPluginClient(cc *grpc.ClientConn) ArchiveSpanReaderPluginClient {\n\treturn &archiveSpanReaderPluginClient{cc}\n}\n\nfunc (c *archiveSpanReaderPluginClient) GetArchiveTrace(ctx context.Context, in *GetTraceRequest, opts ...grpc.CallOption) (ArchiveSpanReaderPlugin_GetArchiveTraceClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &_ArchiveSpanReaderPlugin_serviceDesc.Streams[0], \"/jaeger.storage.v1.ArchiveSpanReaderPlugin/GetArchiveTrace\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &archiveSpanReaderPluginGetArchiveTraceClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype ArchiveSpanReaderPlugin_GetArchiveTraceClient interface {\n\tRecv() (*SpansResponseChunk, error)\n\tgrpc.ClientStream\n}\n\ntype archiveSpanReaderPluginGetArchiveTraceClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *archiveSpanReaderPluginGetArchiveTraceClient) Recv() (*SpansResponseChunk, error) {\n\tm := new(SpansResponseChunk)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// ArchiveSpanReaderPluginServer is the server API for ArchiveSpanReaderPlugin service.\ntype ArchiveSpanReaderPluginServer interface {\n\t// spanstore/Reader\n\tGetArchiveTrace(*GetTraceRequest, ArchiveSpanReaderPlugin_GetArchiveTraceServer) error\n}\n\n// UnimplementedArchiveSpanReaderPluginServer can be embedded to have forward compatible implementations.\ntype UnimplementedArchiveSpanReaderPluginServer struct {\n}\n\nfunc (*UnimplementedArchiveSpanReaderPluginServer) GetArchiveTrace(req *GetTraceRequest, srv ArchiveSpanReaderPlugin_GetArchiveTraceServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method GetArchiveTrace not implemented\")\n}\n\nfunc RegisterArchiveSpanReaderPluginServer(s *grpc.Server, srv ArchiveSpanReaderPluginServer) {\n\ts.RegisterService(&_ArchiveSpanReaderPlugin_serviceDesc, srv)\n}\n\nfunc _ArchiveSpanReaderPlugin_GetArchiveTrace_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(GetTraceRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ArchiveSpanReaderPluginServer).GetArchiveTrace(m, &archiveSpanReaderPluginGetArchiveTraceServer{stream})\n}\n\ntype ArchiveSpanReaderPlugin_GetArchiveTraceServer interface {\n\tSend(*SpansResponseChunk) error\n\tgrpc.ServerStream\n}\n\ntype archiveSpanReaderPluginGetArchiveTraceServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *archiveSpanReaderPluginGetArchiveTraceServer) Send(m *SpansResponseChunk) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nvar _ArchiveSpanReaderPlugin_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.ArchiveSpanReaderPlugin\",\n\tHandlerType: (*ArchiveSpanReaderPluginServer)(nil),\n\tMethods:     []grpc.MethodDesc{},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"GetArchiveTrace\",\n\t\t\tHandler:       _ArchiveSpanReaderPlugin_GetArchiveTrace_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"storage.proto\",\n}\n\n// DependenciesReaderPluginClient is the client API for DependenciesReaderPlugin service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype DependenciesReaderPluginClient interface {\n\t// dependencystore/Reader\n\tGetDependencies(ctx context.Context, in *GetDependenciesRequest, opts ...grpc.CallOption) (*GetDependenciesResponse, error)\n}\n\ntype dependenciesReaderPluginClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewDependenciesReaderPluginClient(cc *grpc.ClientConn) DependenciesReaderPluginClient {\n\treturn &dependenciesReaderPluginClient{cc}\n}\n\nfunc (c *dependenciesReaderPluginClient) GetDependencies(ctx context.Context, in *GetDependenciesRequest, opts ...grpc.CallOption) (*GetDependenciesResponse, error) {\n\tout := new(GetDependenciesResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.DependenciesReaderPlugin/GetDependencies\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// DependenciesReaderPluginServer is the server API for DependenciesReaderPlugin service.\ntype DependenciesReaderPluginServer interface {\n\t// dependencystore/Reader\n\tGetDependencies(context.Context, *GetDependenciesRequest) (*GetDependenciesResponse, error)\n}\n\n// UnimplementedDependenciesReaderPluginServer can be embedded to have forward compatible implementations.\ntype UnimplementedDependenciesReaderPluginServer struct {\n}\n\nfunc (*UnimplementedDependenciesReaderPluginServer) GetDependencies(ctx context.Context, req *GetDependenciesRequest) (*GetDependenciesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetDependencies not implemented\")\n}\n\nfunc RegisterDependenciesReaderPluginServer(s *grpc.Server, srv DependenciesReaderPluginServer) {\n\ts.RegisterService(&_DependenciesReaderPlugin_serviceDesc, srv)\n}\n\nfunc _DependenciesReaderPlugin_GetDependencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetDependenciesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DependenciesReaderPluginServer).GetDependencies(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.DependenciesReaderPlugin/GetDependencies\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DependenciesReaderPluginServer).GetDependencies(ctx, req.(*GetDependenciesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _DependenciesReaderPlugin_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.DependenciesReaderPlugin\",\n\tHandlerType: (*DependenciesReaderPluginServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetDependencies\",\n\t\t\tHandler:    _DependenciesReaderPlugin_GetDependencies_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"storage.proto\",\n}\n\n// PluginCapabilitiesClient is the client API for PluginCapabilities service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype PluginCapabilitiesClient interface {\n\tCapabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error)\n}\n\ntype pluginCapabilitiesClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewPluginCapabilitiesClient(cc *grpc.ClientConn) PluginCapabilitiesClient {\n\treturn &pluginCapabilitiesClient{cc}\n}\n\nfunc (c *pluginCapabilitiesClient) Capabilities(ctx context.Context, in *CapabilitiesRequest, opts ...grpc.CallOption) (*CapabilitiesResponse, error) {\n\tout := new(CapabilitiesResponse)\n\terr := c.cc.Invoke(ctx, \"/jaeger.storage.v1.PluginCapabilities/Capabilities\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// PluginCapabilitiesServer is the server API for PluginCapabilities service.\ntype PluginCapabilitiesServer interface {\n\tCapabilities(context.Context, *CapabilitiesRequest) (*CapabilitiesResponse, error)\n}\n\n// UnimplementedPluginCapabilitiesServer can be embedded to have forward compatible implementations.\ntype UnimplementedPluginCapabilitiesServer struct {\n}\n\nfunc (*UnimplementedPluginCapabilitiesServer) Capabilities(ctx context.Context, req *CapabilitiesRequest) (*CapabilitiesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Capabilities not implemented\")\n}\n\nfunc RegisterPluginCapabilitiesServer(s *grpc.Server, srv PluginCapabilitiesServer) {\n\ts.RegisterService(&_PluginCapabilities_serviceDesc, srv)\n}\n\nfunc _PluginCapabilities_Capabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CapabilitiesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(PluginCapabilitiesServer).Capabilities(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/jaeger.storage.v1.PluginCapabilities/Capabilities\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(PluginCapabilitiesServer).Capabilities(ctx, req.(*CapabilitiesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _PluginCapabilities_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"jaeger.storage.v1.PluginCapabilities\",\n\tHandlerType: (*PluginCapabilitiesServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Capabilities\",\n\t\t\tHandler:    _PluginCapabilities_Capabilities_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"storage.proto\",\n}\n\nfunc (m *GetDependenciesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetDependenciesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetDependenciesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tn1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):])\n\tif err1 != nil {\n\t\treturn 0, err1\n\t}\n\ti -= n1\n\ti = encodeVarintStorage(dAtA, i, uint64(n1))\n\ti--\n\tdAtA[i] = 0x12\n\tn2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):])\n\tif err2 != nil {\n\t\treturn 0, err2\n\t}\n\ti -= n2\n\ti = encodeVarintStorage(dAtA, i, uint64(n2))\n\ti--\n\tdAtA[i] = 0xa\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetDependenciesResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetDependenciesResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetDependenciesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Dependencies) > 0 {\n\t\tfor iNdEx := len(m.Dependencies) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Dependencies[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *WriteSpanRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *WriteSpanRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *WriteSpanRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Span != nil {\n\t\t{\n\t\t\tsize, err := m.Span.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *WriteSpanResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *WriteSpanResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *WriteSpanResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *CloseWriterRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *CloseWriterRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CloseWriterRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *CloseWriterResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *CloseWriterResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CloseWriterResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetTraceRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetTraceRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetTraceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tn4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime):])\n\tif err4 != nil {\n\t\treturn 0, err4\n\t}\n\ti -= n4\n\ti = encodeVarintStorage(dAtA, i, uint64(n4))\n\ti--\n\tdAtA[i] = 0x1a\n\tn5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime):])\n\tif err5 != nil {\n\t\treturn 0, err5\n\t}\n\ti -= n5\n\ti = encodeVarintStorage(dAtA, i, uint64(n5))\n\ti--\n\tdAtA[i] = 0x12\n\t{\n\t\tsize := m.TraceID.Size()\n\t\ti -= size\n\t\tif _, err := m.TraceID.MarshalTo(dAtA[i:]); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t}\n\ti--\n\tdAtA[i] = 0xa\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetServicesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetServicesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetServicesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetServicesResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetServicesResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetServicesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Services) > 0 {\n\t\tfor iNdEx := len(m.Services) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\ti -= len(m.Services[iNdEx])\n\t\t\tcopy(dAtA[i:], m.Services[iNdEx])\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.Services[iNdEx])))\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetOperationsRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetOperationsRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetOperationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.SpanKind) > 0 {\n\t\ti -= len(m.SpanKind)\n\t\tcopy(dAtA[i:], m.SpanKind)\n\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.SpanKind)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Service) > 0 {\n\t\ti -= len(m.Service)\n\t\tcopy(dAtA[i:], m.Service)\n\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.Service)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Operation) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Operation) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Operation) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.SpanKind) > 0 {\n\t\ti -= len(m.SpanKind)\n\t\tcopy(dAtA[i:], m.SpanKind)\n\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.SpanKind)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.Name) > 0 {\n\t\ti -= len(m.Name)\n\t\tcopy(dAtA[i:], m.Name)\n\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.Name)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *GetOperationsResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *GetOperationsResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *GetOperationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Operations) > 0 {\n\t\tfor iNdEx := len(m.Operations) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Operations[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x12\n\t\t}\n\t}\n\tif len(m.OperationNames) > 0 {\n\t\tfor iNdEx := len(m.OperationNames) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\ti -= len(m.OperationNames[iNdEx])\n\t\t\tcopy(dAtA[i:], m.OperationNames[iNdEx])\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.OperationNames[iNdEx])))\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *TraceQueryParameters) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *TraceQueryParameters) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *TraceQueryParameters) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.NumTraces != 0 {\n\t\ti = encodeVarintStorage(dAtA, i, uint64(m.NumTraces))\n\t\ti--\n\t\tdAtA[i] = 0x40\n\t}\n\tn6, err6 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.DurationMax, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMax):])\n\tif err6 != nil {\n\t\treturn 0, err6\n\t}\n\ti -= n6\n\ti = encodeVarintStorage(dAtA, i, uint64(n6))\n\ti--\n\tdAtA[i] = 0x3a\n\tn7, err7 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.DurationMin, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMin):])\n\tif err7 != nil {\n\t\treturn 0, err7\n\t}\n\ti -= n7\n\ti = encodeVarintStorage(dAtA, i, uint64(n7))\n\ti--\n\tdAtA[i] = 0x32\n\tn8, err8 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTimeMax, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMax):])\n\tif err8 != nil {\n\t\treturn 0, err8\n\t}\n\ti -= n8\n\ti = encodeVarintStorage(dAtA, i, uint64(n8))\n\ti--\n\tdAtA[i] = 0x2a\n\tn9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTimeMin, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMin):])\n\tif err9 != nil {\n\t\treturn 0, err9\n\t}\n\ti -= n9\n\ti = encodeVarintStorage(dAtA, i, uint64(n9))\n\ti--\n\tdAtA[i] = 0x22\n\tif len(m.Tags) > 0 {\n\t\tfor k := range m.Tags {\n\t\t\tv := m.Tags[k]\n\t\t\tbaseI := i\n\t\t\ti -= len(v)\n\t\t\tcopy(dAtA[i:], v)\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(len(v)))\n\t\t\ti--\n\t\t\tdAtA[i] = 0x12\n\t\t\ti -= len(k)\n\t\t\tcopy(dAtA[i:], k)\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(len(k)))\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(baseI-i))\n\t\t\ti--\n\t\t\tdAtA[i] = 0x1a\n\t\t}\n\t}\n\tif len(m.OperationName) > 0 {\n\t\ti -= len(m.OperationName)\n\t\tcopy(dAtA[i:], m.OperationName)\n\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.OperationName)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif len(m.ServiceName) > 0 {\n\t\ti -= len(m.ServiceName)\n\t\tcopy(dAtA[i:], m.ServiceName)\n\t\ti = encodeVarintStorage(dAtA, i, uint64(len(m.ServiceName)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FindTracesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FindTracesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FindTracesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Query != nil {\n\t\t{\n\t\t\tsize, err := m.Query.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *SpansResponseChunk) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *SpansResponseChunk) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *SpansResponseChunk) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.Spans) > 0 {\n\t\tfor iNdEx := len(m.Spans) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.Spans[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FindTraceIDsRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FindTraceIDsRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FindTraceIDsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.Query != nil {\n\t\t{\n\t\t\tsize, err := m.Query.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *FindTraceIDsResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *FindTraceIDsResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *FindTraceIDsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif len(m.TraceIDs) > 0 {\n\t\tfor iNdEx := len(m.TraceIDs) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize := m.TraceIDs[iNdEx].Size()\n\t\t\t\ti -= size\n\t\t\t\tif _, err := m.TraceIDs[iNdEx].MarshalTo(dAtA[i:]); err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti = encodeVarintStorage(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0xa\n\t\t}\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *CapabilitiesRequest) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *CapabilitiesRequest) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CapabilitiesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *CapabilitiesResponse) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *CapabilitiesResponse) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *CapabilitiesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\ti -= len(m.XXX_unrecognized)\n\t\tcopy(dAtA[i:], m.XXX_unrecognized)\n\t}\n\tif m.StreamingSpanWriter {\n\t\ti--\n\t\tif m.StreamingSpanWriter {\n\t\t\tdAtA[i] = 1\n\t\t} else {\n\t\t\tdAtA[i] = 0\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif m.ArchiveSpanWriter {\n\t\ti--\n\t\tif m.ArchiveSpanWriter {\n\t\t\tdAtA[i] = 1\n\t\t} else {\n\t\t\tdAtA[i] = 0\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.ArchiveSpanReader {\n\t\ti--\n\t\tif m.ArchiveSpanReader {\n\t\t\tdAtA[i] = 1\n\t\t} else {\n\t\t\tdAtA[i] = 0\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc encodeVarintStorage(dAtA []byte, offset int, v uint64) int {\n\toffset -= sovStorage(v)\n\tbase := offset\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn base\n}\nfunc (m *GetDependenciesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime)\n\tn += 1 + l + sovStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime)\n\tn += 1 + l + sovStorage(uint64(l))\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetDependenciesResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Dependencies) > 0 {\n\t\tfor _, e := range m.Dependencies {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *WriteSpanRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Span != nil {\n\t\tl = m.Span.Size()\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *WriteSpanResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *CloseWriterRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *CloseWriterResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetTraceRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = m.TraceID.Size()\n\tn += 1 + l + sovStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTime)\n\tn += 1 + l + sovStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTime)\n\tn += 1 + l + sovStorage(uint64(l))\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetServicesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetServicesResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Services) > 0 {\n\t\tfor _, s := range m.Services {\n\t\t\tl = len(s)\n\t\t\tn += 1 + l + sovStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetOperationsRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Service)\n\tif l > 0 {\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tl = len(m.SpanKind)\n\tif l > 0 {\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *Operation) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Name)\n\tif l > 0 {\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tl = len(m.SpanKind)\n\tif l > 0 {\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *GetOperationsResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.OperationNames) > 0 {\n\t\tfor _, s := range m.OperationNames {\n\t\t\tl = len(s)\n\t\t\tn += 1 + l + sovStorage(uint64(l))\n\t\t}\n\t}\n\tif len(m.Operations) > 0 {\n\t\tfor _, e := range m.Operations {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *TraceQueryParameters) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.ServiceName)\n\tif l > 0 {\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tl = len(m.OperationName)\n\tif l > 0 {\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tif len(m.Tags) > 0 {\n\t\tfor k, v := range m.Tags {\n\t\t\t_ = k\n\t\t\t_ = v\n\t\t\tmapEntrySize := 1 + len(k) + sovStorage(uint64(len(k))) + 1 + len(v) + sovStorage(uint64(len(v)))\n\t\t\tn += mapEntrySize + 1 + sovStorage(uint64(mapEntrySize))\n\t\t}\n\t}\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMin)\n\tn += 1 + l + sovStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTimeMax)\n\tn += 1 + l + sovStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMin)\n\tn += 1 + l + sovStorage(uint64(l))\n\tl = github_com_gogo_protobuf_types.SizeOfStdDuration(m.DurationMax)\n\tn += 1 + l + sovStorage(uint64(l))\n\tif m.NumTraces != 0 {\n\t\tn += 1 + sovStorage(uint64(m.NumTraces))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FindTracesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Query != nil {\n\t\tl = m.Query.Size()\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *SpansResponseChunk) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.Spans) > 0 {\n\t\tfor _, e := range m.Spans {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FindTraceIDsRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Query != nil {\n\t\tl = m.Query.Size()\n\t\tn += 1 + l + sovStorage(uint64(l))\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *FindTraceIDsResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif len(m.TraceIDs) > 0 {\n\t\tfor _, e := range m.TraceIDs {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovStorage(uint64(l))\n\t\t}\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *CapabilitiesRequest) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc (m *CapabilitiesResponse) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.ArchiveSpanReader {\n\t\tn += 2\n\t}\n\tif m.ArchiveSpanWriter {\n\t\tn += 2\n\t}\n\tif m.StreamingSpanWriter {\n\t\tn += 2\n\t}\n\tif m.XXX_unrecognized != nil {\n\t\tn += len(m.XXX_unrecognized)\n\t}\n\treturn n\n}\n\nfunc sovStorage(x uint64) (n int) {\n\treturn (math_bits.Len64(x|1) + 6) / 7\n}\nfunc sozStorage(x uint64) (n int) {\n\treturn sovStorage(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *GetDependenciesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field EndTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetDependenciesResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetDependenciesResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Dependencies\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Dependencies = append(m.Dependencies, v1.DependencyLink{})\n\t\t\tif err := m.Dependencies[len(m.Dependencies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *WriteSpanRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: WriteSpanRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: WriteSpanRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Span\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Span == nil {\n\t\t\t\tm.Span = &v1.Span{}\n\t\t\t}\n\t\t\tif err := m.Span.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *WriteSpanResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: WriteSpanResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: WriteSpanResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *CloseWriterRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: CloseWriterRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: CloseWriterRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *CloseWriterResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: CloseWriterResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: CloseWriterResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetTraceRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetTraceRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetTraceRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field TraceID\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := m.TraceID.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field EndTime\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndTime, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetServicesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetServicesResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetServicesResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Services\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Services = append(m.Services, string(dAtA[iNdEx:postIndex]))\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetOperationsRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Service\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Service = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SpanKind\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.SpanKind = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Operation) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Operation: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Operation: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Name\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Name = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field SpanKind\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.SpanKind = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *GetOperationsResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: GetOperationsResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OperationNames\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.OperationNames = append(m.OperationNames, string(dAtA[iNdEx:postIndex]))\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Operations\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Operations = append(m.Operations, &Operation{})\n\t\t\tif err := m.Operations[len(m.Operations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *TraceQueryParameters) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: TraceQueryParameters: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: TraceQueryParameters: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ServiceName\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.ServiceName = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OperationName\", wireType)\n\t\t\t}\n\t\t\tvar stringLen uint64\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tstringLen |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tintStringLen := int(stringLen)\n\t\t\tif intStringLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + intStringLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.OperationName = string(dAtA[iNdEx:postIndex])\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Tags\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Tags == nil {\n\t\t\t\tm.Tags = make(map[string]string)\n\t\t\t}\n\t\t\tvar mapkey string\n\t\t\tvar mapvalue string\n\t\t\tfor iNdEx < postIndex {\n\t\t\t\tentryPreIndex := iNdEx\n\t\t\t\tvar wire uint64\n\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t\t}\n\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\tiNdEx++\n\t\t\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfieldNum := int32(wire >> 3)\n\t\t\t\tif fieldNum == 1 {\n\t\t\t\t\tvar stringLenmapkey uint64\n\t\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t\t}\n\t\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\t\tiNdEx++\n\t\t\t\t\t\tstringLenmapkey |= uint64(b&0x7F) << shift\n\t\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tintStringLenmapkey := int(stringLenmapkey)\n\t\t\t\t\tif intStringLenmapkey < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t\t\t}\n\t\t\t\t\tpostStringIndexmapkey := iNdEx + intStringLenmapkey\n\t\t\t\t\tif postStringIndexmapkey < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t\t\t}\n\t\t\t\t\tif postStringIndexmapkey > l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tmapkey = string(dAtA[iNdEx:postStringIndexmapkey])\n\t\t\t\t\tiNdEx = postStringIndexmapkey\n\t\t\t\t} else if fieldNum == 2 {\n\t\t\t\t\tvar stringLenmapvalue uint64\n\t\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t\t}\n\t\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\t\tiNdEx++\n\t\t\t\t\t\tstringLenmapvalue |= uint64(b&0x7F) << shift\n\t\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tintStringLenmapvalue := int(stringLenmapvalue)\n\t\t\t\t\tif intStringLenmapvalue < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t\t\t}\n\t\t\t\t\tpostStringIndexmapvalue := iNdEx + intStringLenmapvalue\n\t\t\t\t\tif postStringIndexmapvalue < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t\t\t}\n\t\t\t\t\tif postStringIndexmapvalue > l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tmapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])\n\t\t\t\t\tiNdEx = postStringIndexmapvalue\n\t\t\t\t} else {\n\t\t\t\t\tiNdEx = entryPreIndex\n\t\t\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t\t\t}\n\t\t\t\t\tif (iNdEx + skippy) > postIndex {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tiNdEx += skippy\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Tags[mapkey] = mapvalue\n\t\t\tiNdEx = postIndex\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTimeMin\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTimeMin, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StartTimeMax\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.StartTimeMax, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DurationMin\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.DurationMin, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field DurationMax\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.DurationMax, dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 8:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field NumTraces\", wireType)\n\t\t\t}\n\t\t\tm.NumTraces = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.NumTraces |= int32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FindTracesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FindTracesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FindTracesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Query\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Query == nil {\n\t\t\t\tm.Query = &TraceQueryParameters{}\n\t\t\t}\n\t\t\tif err := m.Query.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *SpansResponseChunk) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: SpansResponseChunk: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: SpansResponseChunk: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Spans\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Spans = append(m.Spans, v1.Span{})\n\t\t\tif err := m.Spans[len(m.Spans)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FindTraceIDsRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FindTraceIDsRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FindTraceIDsRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Query\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Query == nil {\n\t\t\t\tm.Query = &TraceQueryParameters{}\n\t\t\t}\n\t\t\tif err := m.Query.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *FindTraceIDsResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: FindTraceIDsResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: FindTraceIDsResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field TraceIDs\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tvar v github_com_jaegertracing_jaeger_idl_model_v1.TraceID\n\t\t\tm.TraceIDs = append(m.TraceIDs, v)\n\t\t\tif err := m.TraceIDs[len(m.TraceIDs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *CapabilitiesRequest) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: CapabilitiesRequest: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: CapabilitiesRequest: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *CapabilitiesResponse) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: CapabilitiesResponse: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: CapabilitiesResponse: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ArchiveSpanReader\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.ArchiveSpanReader = bool(v != 0)\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ArchiveSpanWriter\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.ArchiveSpanWriter = bool(v != 0)\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field StreamingSpanWriter\", wireType)\n\t\t\t}\n\t\t\tvar v int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tv |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.StreamingSpanWriter = bool(v != 0)\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipStorage(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipStorage(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tdepth := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowStorage\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowStorage\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthStorage\n\t\t\t}\n\t\t\tiNdEx += length\n\t\tcase 3:\n\t\t\tdepth++\n\t\tcase 4:\n\t\t\tif depth == 0 {\n\t\t\t\treturn 0, ErrUnexpectedEndOfGroupStorage\n\t\t\t}\n\t\t\tdepth--\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t\tif iNdEx < 0 {\n\t\t\treturn 0, ErrInvalidLengthStorage\n\t\t}\n\t\tif depth == 0 {\n\t\t\treturn iNdEx, nil\n\t\t}\n\t}\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nvar (\n\tErrInvalidLengthStorage        = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowStorage          = fmt.Errorf(\"proto: integer overflow\")\n\tErrUnexpectedEndOfGroupStorage = fmt.Errorf(\"proto: unexpected end of group\")\n)\n"
  },
  {
    "path": "internal/proto-gen/zipkin/zipkin.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: zipkin.proto\n\npackage zipkin_proto3\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\n// When present, kind clarifies timestamp, duration and remote_endpoint. When\n// absent, the span is local or incomplete. Unlike client and server, there\n// is no direct critical path latency relationship between producer and\n// consumer spans.\ntype Span_Kind int32\n\nconst (\n\t// Default value interpreted as absent.\n\tSpan_SPAN_KIND_UNSPECIFIED Span_Kind = 0\n\t// The span represents the client side of an RPC operation, implying the\n\t// following:\n\t//\n\t// timestamp is the moment a request was sent to the server.\n\t// duration is the delay until a response or an error was received.\n\t// remote_endpoint is the server.\n\tSpan_CLIENT Span_Kind = 1\n\t// The span represents the server side of an RPC operation, implying the\n\t// following:\n\t//\n\t// timestamp is the moment a client request was received.\n\t// duration is the delay until a response was sent or an error.\n\t// remote_endpoint is the client.\n\tSpan_SERVER Span_Kind = 2\n\t// The span represents production of a message to a remote broker, implying\n\t// the following:\n\t//\n\t// timestamp is the moment a message was sent to a destination.\n\t// duration is the delay sending the message, such as batching.\n\t// remote_endpoint is the broker.\n\tSpan_PRODUCER Span_Kind = 3\n\t// The span represents consumption of a message from a remote broker, not\n\t// time spent servicing it. For example, a message processor would be an\n\t// in-process child span of a consumer. Consumer spans imply the following:\n\t//\n\t// timestamp is the moment a message was received from an origin.\n\t// duration is the delay consuming the message, such as from backlog.\n\t// remote_endpoint is the broker.\n\tSpan_CONSUMER Span_Kind = 4\n)\n\nvar Span_Kind_name = map[int32]string{\n\t0: \"SPAN_KIND_UNSPECIFIED\",\n\t1: \"CLIENT\",\n\t2: \"SERVER\",\n\t3: \"PRODUCER\",\n\t4: \"CONSUMER\",\n}\n\nvar Span_Kind_value = map[string]int32{\n\t\"SPAN_KIND_UNSPECIFIED\": 0,\n\t\"CLIENT\":                1,\n\t\"SERVER\":                2,\n\t\"PRODUCER\":              3,\n\t\"CONSUMER\":              4,\n}\n\nfunc (x Span_Kind) String() string {\n\treturn proto.EnumName(Span_Kind_name, int32(x))\n}\n\nfunc (Span_Kind) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_ab863b5fa670a281, []int{0, 0}\n}\n\n// A span is a single-host view of an operation. A trace is a series of spans\n// (often RPC calls) which nest to form a latency tree. Spans are in the same\n// trace when they share the same trace ID. The parent_id field establishes the\n// position of one span in the tree.\n//\n// The root span is where parent_id is Absent and usually has the longest\n// duration in the trace. However, nested asynchronous work can materialize as\n// child spans whose duration exceed the root span.\n//\n// Spans usually represent remote activity such as RPC calls, or messaging\n// producers and consumers. However, they can also represent in-process\n// activity in any position of the trace. For example, a root span could\n// represent a server receiving an initial client request. A root span could\n// also represent a scheduled job that has no remote context.\n//\n// Encoding notes:\n//\n// Epoch timestamp are encoded fixed64 as varint would also be 8 bytes, and more\n// expensive to encode and size. Duration is stored uint64, as often the numbers\n// are quite small.\n//\n// Default values are ok, as only natural numbers are used. For example, zero is\n// an invalid timestamp and an invalid duration, false values for debug or shared\n// are ignorable, and zero-length strings also coerce to null.\n//\n// The next id is 14.\n//\n// Note fields up to 15 take 1 byte to encode. Take care when adding new fields\n// https://developers.google.com/protocol-buffers/docs/proto3#assigning-tags\ntype Span struct {\n\t// Randomly generated, unique identifier for a trace, set on all spans within\n\t// it.\n\t//\n\t// This field is required and encoded as 8 or 16 bytes, in big endian byte\n\t// order.\n\tTraceId []byte `protobuf:\"bytes,1,opt,name=trace_id,json=traceId,proto3\" json:\"trace_id,omitempty\"`\n\t// The parent span ID or absent if this the root span in a trace.\n\tParentId []byte `protobuf:\"bytes,2,opt,name=parent_id,json=parentId,proto3\" json:\"parent_id,omitempty\"`\n\t// Unique identifier for this operation within the trace.\n\t//\n\t// This field is required and encoded as 8 opaque bytes.\n\tId []byte `protobuf:\"bytes,3,opt,name=id,proto3\" json:\"id,omitempty\"`\n\t// When present, used to interpret remote_endpoint\n\tKind Span_Kind `protobuf:\"varint,4,opt,name=kind,proto3,enum=zipkin.proto3.Span_Kind\" json:\"kind,omitempty\"`\n\t// The logical operation this span represents in lowercase (e.g. rpc method).\n\t// Leave absent if unknown.\n\t//\n\t// As these are lookup labels, take care to ensure names are low cardinality.\n\t// For example, do not embed variables into the name.\n\tName string `protobuf:\"bytes,5,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Epoch microseconds of the start of this span, possibly absent if\n\t// incomplete.\n\t//\n\t// For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC\n\t//\n\t// This value should be set directly by instrumentation, using the most\n\t// precise value possible. For example, gettimeofday or multiplying epoch\n\t// millis by 1000.\n\t//\n\t// There are three known edge-cases where this could be reported absent.\n\t// - A span was allocated but never started (ex not yet received a timestamp)\n\t// - The span's start event was lost\n\t// - Data about a completed span (ex tags) were sent after the fact\n\tTimestamp uint64 `protobuf:\"fixed64,6,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\t// Duration in microseconds of the critical path, if known. Durations of less\n\t// than one are rounded up. Duration of children can be longer than their\n\t// parents due to asynchronous operations.\n\t//\n\t// For example 150 milliseconds is 150000 microseconds.\n\tDuration uint64 `protobuf:\"varint,7,opt,name=duration,proto3\" json:\"duration,omitempty\"`\n\t// The host that recorded this span, primarily for query by service name.\n\t//\n\t// Instrumentation should always record this. Usually, absent implies late\n\t// data. The IP address corresponding to this is usually the site local or\n\t// advertised service address. When present, the port indicates the listen\n\t// port.\n\tLocalEndpoint *Endpoint `protobuf:\"bytes,8,opt,name=local_endpoint,json=localEndpoint,proto3\" json:\"local_endpoint,omitempty\"`\n\t// When an RPC (or messaging) span, indicates the other side of the\n\t// connection.\n\t//\n\t// By recording the remote endpoint, your trace will contain network context\n\t// even if the peer is not tracing. For example, you can record the IP from\n\t// the \"X-Forwarded-For\" header or the service name and socket of a remote\n\t// peer.\n\tRemoteEndpoint *Endpoint `protobuf:\"bytes,9,opt,name=remote_endpoint,json=remoteEndpoint,proto3\" json:\"remote_endpoint,omitempty\"`\n\t// Associates events that explain latency with the time they happened.\n\tAnnotations []*Annotation `protobuf:\"bytes,10,rep,name=annotations,proto3\" json:\"annotations,omitempty\"`\n\t// Tags give your span context for search, viewing and analysis.\n\t//\n\t// For example, a key \"your_app.version\" would let you lookup traces by\n\t// version. A tag \"sql.query\" isn't searchable, but it can help in debugging\n\t// when viewing a trace.\n\tTags map[string]string `protobuf:\"bytes,11,rep,name=tags,proto3\" json:\"tags,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\t// True is a request to store this span even if it overrides sampling policy.\n\t//\n\t// This is true when the \"X-B3-Flags\" header has a value of 1.\n\tDebug bool `protobuf:\"varint,12,opt,name=debug,proto3\" json:\"debug,omitempty\"`\n\t// True if we are contributing to a span started by another tracer (ex on a\n\t// different host).\n\tShared               bool     `protobuf:\"varint,13,opt,name=shared,proto3\" json:\"shared,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Span) Reset()         { *m = Span{} }\nfunc (m *Span) String() string { return proto.CompactTextString(m) }\nfunc (*Span) ProtoMessage()    {}\nfunc (*Span) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_ab863b5fa670a281, []int{0}\n}\nfunc (m *Span) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Span.Unmarshal(m, b)\n}\nfunc (m *Span) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Span.Marshal(b, m, deterministic)\n}\nfunc (m *Span) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Span.Merge(m, src)\n}\nfunc (m *Span) XXX_Size() int {\n\treturn xxx_messageInfo_Span.Size(m)\n}\nfunc (m *Span) XXX_DiscardUnknown() {\n\txxx_messageInfo_Span.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Span proto.InternalMessageInfo\n\nfunc (m *Span) GetTraceId() []byte {\n\tif m != nil {\n\t\treturn m.TraceId\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetParentId() []byte {\n\tif m != nil {\n\t\treturn m.ParentId\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetId() []byte {\n\tif m != nil {\n\t\treturn m.Id\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetKind() Span_Kind {\n\tif m != nil {\n\t\treturn m.Kind\n\t}\n\treturn Span_SPAN_KIND_UNSPECIFIED\n}\n\nfunc (m *Span) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Span) GetTimestamp() uint64 {\n\tif m != nil {\n\t\treturn m.Timestamp\n\t}\n\treturn 0\n}\n\nfunc (m *Span) GetDuration() uint64 {\n\tif m != nil {\n\t\treturn m.Duration\n\t}\n\treturn 0\n}\n\nfunc (m *Span) GetLocalEndpoint() *Endpoint {\n\tif m != nil {\n\t\treturn m.LocalEndpoint\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetRemoteEndpoint() *Endpoint {\n\tif m != nil {\n\t\treturn m.RemoteEndpoint\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetAnnotations() []*Annotation {\n\tif m != nil {\n\t\treturn m.Annotations\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetTags() map[string]string {\n\tif m != nil {\n\t\treturn m.Tags\n\t}\n\treturn nil\n}\n\nfunc (m *Span) GetDebug() bool {\n\tif m != nil {\n\t\treturn m.Debug\n\t}\n\treturn false\n}\n\nfunc (m *Span) GetShared() bool {\n\tif m != nil {\n\t\treturn m.Shared\n\t}\n\treturn false\n}\n\n// The network context of a node in the service graph.\n//\n// The next id is 5.\ntype Endpoint struct {\n\t// Lower-case label of this node in the service graph, such as \"favstar\".\n\t// Leave absent if unknown.\n\t//\n\t// This is a primary label for trace lookup and aggregation, so it should be\n\t// intuitive and consistent. Many use a name from service discovery.\n\tServiceName string `protobuf:\"bytes,1,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\t// 4 byte representation of the primary IPv4 address associated with this\n\t// connection. Absent if unknown.\n\tIpv4 []byte `protobuf:\"bytes,2,opt,name=ipv4,proto3\" json:\"ipv4,omitempty\"`\n\t// 16 byte representation of the primary IPv6 address associated with this\n\t// connection. Absent if unknown.\n\t//\n\t// Prefer using the ipv4 field for mapped addresses.\n\tIpv6 []byte `protobuf:\"bytes,3,opt,name=ipv6,proto3\" json:\"ipv6,omitempty\"`\n\t// Depending on context, this could be a listen port or the client-side of a\n\t// socket. Absent if unknown.\n\tPort                 int32    `protobuf:\"varint,4,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Endpoint) Reset()         { *m = Endpoint{} }\nfunc (m *Endpoint) String() string { return proto.CompactTextString(m) }\nfunc (*Endpoint) ProtoMessage()    {}\nfunc (*Endpoint) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_ab863b5fa670a281, []int{1}\n}\nfunc (m *Endpoint) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Endpoint.Unmarshal(m, b)\n}\nfunc (m *Endpoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Endpoint.Marshal(b, m, deterministic)\n}\nfunc (m *Endpoint) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Endpoint.Merge(m, src)\n}\nfunc (m *Endpoint) XXX_Size() int {\n\treturn xxx_messageInfo_Endpoint.Size(m)\n}\nfunc (m *Endpoint) XXX_DiscardUnknown() {\n\txxx_messageInfo_Endpoint.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Endpoint proto.InternalMessageInfo\n\nfunc (m *Endpoint) GetServiceName() string {\n\tif m != nil {\n\t\treturn m.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (m *Endpoint) GetIpv4() []byte {\n\tif m != nil {\n\t\treturn m.Ipv4\n\t}\n\treturn nil\n}\n\nfunc (m *Endpoint) GetIpv6() []byte {\n\tif m != nil {\n\t\treturn m.Ipv6\n\t}\n\treturn nil\n}\n\nfunc (m *Endpoint) GetPort() int32 {\n\tif m != nil {\n\t\treturn m.Port\n\t}\n\treturn 0\n}\n\n// Associates an event that explains latency with a timestamp.\n// Unlike log statements, annotations are often codes. Ex. \"ws\" for WireSend\n//\n// The next id is 3.\ntype Annotation struct {\n\t// Epoch microseconds of this event.\n\t//\n\t// For example, 1502787600000000 corresponds to 2017-08-15 09:00 UTC\n\t//\n\t// This value should be set directly by instrumentation, using the most\n\t// precise value possible. For example, gettimeofday or multiplying epoch\n\t// millis by 1000.\n\tTimestamp uint64 `protobuf:\"fixed64,1,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\t// Usually a short tag indicating an event, like \"error\"\n\t//\n\t// While possible to add larger data, such as garbage collection details, low\n\t// cardinality event names both keep the size of spans down and also are easy\n\t// to search against.\n\tValue                string   `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Annotation) Reset()         { *m = Annotation{} }\nfunc (m *Annotation) String() string { return proto.CompactTextString(m) }\nfunc (*Annotation) ProtoMessage()    {}\nfunc (*Annotation) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_ab863b5fa670a281, []int{2}\n}\nfunc (m *Annotation) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Annotation.Unmarshal(m, b)\n}\nfunc (m *Annotation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Annotation.Marshal(b, m, deterministic)\n}\nfunc (m *Annotation) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Annotation.Merge(m, src)\n}\nfunc (m *Annotation) XXX_Size() int {\n\treturn xxx_messageInfo_Annotation.Size(m)\n}\nfunc (m *Annotation) XXX_DiscardUnknown() {\n\txxx_messageInfo_Annotation.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Annotation proto.InternalMessageInfo\n\nfunc (m *Annotation) GetTimestamp() uint64 {\n\tif m != nil {\n\t\treturn m.Timestamp\n\t}\n\treturn 0\n}\n\nfunc (m *Annotation) GetValue() string {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn \"\"\n}\n\n// A list of spans with possibly different trace ids, in no particular order.\n//\n// This is used for all transports: POST, Kafka messages etc. No other fields\n// are expected, This message facilitates the mechanics of encoding a list, as\n// a field number is required. The name of this type is the same in the OpenApi\n// aka Swagger specification. https://zipkin.io/zipkin-api/#/default/post_spans\ntype ListOfSpans struct {\n\tSpans                []*Span  `protobuf:\"bytes,1,rep,name=spans,proto3\" json:\"spans,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *ListOfSpans) Reset()         { *m = ListOfSpans{} }\nfunc (m *ListOfSpans) String() string { return proto.CompactTextString(m) }\nfunc (*ListOfSpans) ProtoMessage()    {}\nfunc (*ListOfSpans) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_ab863b5fa670a281, []int{3}\n}\nfunc (m *ListOfSpans) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_ListOfSpans.Unmarshal(m, b)\n}\nfunc (m *ListOfSpans) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_ListOfSpans.Marshal(b, m, deterministic)\n}\nfunc (m *ListOfSpans) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ListOfSpans.Merge(m, src)\n}\nfunc (m *ListOfSpans) XXX_Size() int {\n\treturn xxx_messageInfo_ListOfSpans.Size(m)\n}\nfunc (m *ListOfSpans) XXX_DiscardUnknown() {\n\txxx_messageInfo_ListOfSpans.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ListOfSpans proto.InternalMessageInfo\n\nfunc (m *ListOfSpans) GetSpans() []*Span {\n\tif m != nil {\n\t\treturn m.Spans\n\t}\n\treturn nil\n}\n\n// Response for SpanService/Report RPC. This response currently does not return\n// any information beyond indicating that the request has finished. That said,\n// it may be extended in the future.\ntype ReportResponse struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *ReportResponse) Reset()         { *m = ReportResponse{} }\nfunc (m *ReportResponse) String() string { return proto.CompactTextString(m) }\nfunc (*ReportResponse) ProtoMessage()    {}\nfunc (*ReportResponse) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_ab863b5fa670a281, []int{4}\n}\nfunc (m *ReportResponse) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_ReportResponse.Unmarshal(m, b)\n}\nfunc (m *ReportResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_ReportResponse.Marshal(b, m, deterministic)\n}\nfunc (m *ReportResponse) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ReportResponse.Merge(m, src)\n}\nfunc (m *ReportResponse) XXX_Size() int {\n\treturn xxx_messageInfo_ReportResponse.Size(m)\n}\nfunc (m *ReportResponse) XXX_DiscardUnknown() {\n\txxx_messageInfo_ReportResponse.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ReportResponse proto.InternalMessageInfo\n\nfunc init() {\n\tproto.RegisterEnum(\"zipkin.proto3.Span_Kind\", Span_Kind_name, Span_Kind_value)\n\tproto.RegisterType((*Span)(nil), \"zipkin.proto3.Span\")\n\tproto.RegisterMapType((map[string]string)(nil), \"zipkin.proto3.Span.TagsEntry\")\n\tproto.RegisterType((*Endpoint)(nil), \"zipkin.proto3.Endpoint\")\n\tproto.RegisterType((*Annotation)(nil), \"zipkin.proto3.Annotation\")\n\tproto.RegisterType((*ListOfSpans)(nil), \"zipkin.proto3.ListOfSpans\")\n\tproto.RegisterType((*ReportResponse)(nil), \"zipkin.proto3.ReportResponse\")\n}\n\nfunc init() { proto.RegisterFile(\"zipkin.proto\", fileDescriptor_ab863b5fa670a281) }\n\nvar fileDescriptor_ab863b5fa670a281 = []byte{\n\t// 563 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xdf, 0x6f, 0xd3, 0x3c,\n\t0x14, 0x9d, 0xdb, 0x34, 0x4b, 0x6e, 0xba, 0x7c, 0x91, 0x3f, 0x7e, 0x78, 0x05, 0xa4, 0x90, 0xa7,\n\t0x20, 0xa1, 0x4a, 0x14, 0x04, 0x13, 0x48, 0x68, 0xa3, 0x0b, 0x52, 0xb4, 0x91, 0x55, 0xce, 0xca,\n\t0x6b, 0xe5, 0x2d, 0x66, 0x58, 0x5b, 0x9d, 0x28, 0xf1, 0x26, 0x8d, 0x3f, 0x9d, 0x27, 0x64, 0x27,\n\t0x74, 0x5b, 0x55, 0xf1, 0x76, 0xce, 0xf1, 0xc9, 0xb1, 0x73, 0xef, 0x81, 0xe1, 0x2f, 0x51, 0x5d,\n\t0x0a, 0x39, 0xae, 0xea, 0x52, 0x95, 0x78, 0xe7, 0x3e, 0x7b, 0x1b, 0xfd, 0xb6, 0xc0, 0xca, 0x2b,\n\t0x26, 0xf1, 0x2e, 0x38, 0xaa, 0x66, 0xe7, 0x7c, 0x21, 0x0a, 0x82, 0x42, 0x14, 0x0f, 0xe9, 0xb6,\n\t0xe1, 0x69, 0x81, 0x9f, 0x81, 0x5b, 0xb1, 0x9a, 0x4b, 0xa5, 0xcf, 0x7a, 0xe6, 0xcc, 0x69, 0x85,\n\t0xb4, 0xc0, 0x3e, 0xf4, 0x44, 0x41, 0xfa, 0x46, 0xed, 0x89, 0x02, 0xbf, 0x06, 0xeb, 0x52, 0xc8,\n\t0x82, 0x58, 0x21, 0x8a, 0xfd, 0x09, 0x19, 0x3f, 0xb8, 0x6e, 0xac, 0xaf, 0x1a, 0x1f, 0x09, 0x59,\n\t0x50, 0xe3, 0xc2, 0x18, 0x2c, 0xc9, 0x96, 0x9c, 0x0c, 0x42, 0x14, 0xbb, 0xd4, 0x60, 0xfc, 0x1c,\n\t0x5c, 0x25, 0x96, 0xbc, 0x51, 0x6c, 0x59, 0x11, 0x3b, 0x44, 0xb1, 0x4d, 0xef, 0x04, 0x3c, 0x02,\n\t0xa7, 0xb8, 0xae, 0x99, 0x12, 0xa5, 0x24, 0xdb, 0x21, 0x8a, 0x2d, 0xba, 0xe2, 0xf8, 0x33, 0xf8,\n\t0x57, 0xe5, 0x39, 0xbb, 0x5a, 0x70, 0x59, 0x54, 0xa5, 0x90, 0x8a, 0x38, 0x21, 0x8a, 0xbd, 0xc9,\n\t0xd3, 0xb5, 0x57, 0x24, 0xdd, 0x31, 0xdd, 0x31, 0xf6, 0xbf, 0x14, 0xef, 0xc3, 0x7f, 0x35, 0x5f,\n\t0x96, 0x8a, 0xdf, 0x05, 0xb8, 0xff, 0x0e, 0xf0, 0x5b, 0xff, 0x2a, 0xe1, 0x13, 0x78, 0x4c, 0xca,\n\t0x52, 0x99, 0xf7, 0x34, 0x04, 0xc2, 0x7e, 0xec, 0x4d, 0x76, 0xd7, 0xbe, 0x3e, 0x58, 0x39, 0xe8,\n\t0x7d, 0x37, 0x7e, 0x03, 0x96, 0x62, 0x17, 0x0d, 0xf1, 0xcc, 0x57, 0x2f, 0x36, 0x8d, 0xee, 0x94,\n\t0x5d, 0x34, 0x89, 0x54, 0xf5, 0x2d, 0x35, 0x56, 0xfc, 0x08, 0x06, 0x05, 0x3f, 0xbb, 0xbe, 0x20,\n\t0xc3, 0x10, 0xc5, 0x0e, 0x6d, 0x09, 0x7e, 0x02, 0x76, 0xf3, 0x93, 0xd5, 0xbc, 0x20, 0x3b, 0x46,\n\t0xee, 0xd8, 0xe8, 0x03, 0xb8, 0xab, 0x00, 0x1c, 0x40, 0xff, 0x92, 0xdf, 0x9a, 0x5d, 0xbb, 0x54,\n\t0x43, 0x1d, 0x76, 0xc3, 0xae, 0xae, 0xb9, 0xd9, 0xb1, 0x4b, 0x5b, 0xf2, 0xb1, 0xb7, 0x87, 0xa2,\n\t0x39, 0x58, 0x7a, 0x69, 0x78, 0x17, 0x1e, 0xe7, 0xb3, 0x83, 0x6c, 0x71, 0x94, 0x66, 0x87, 0x8b,\n\t0x79, 0x96, 0xcf, 0x92, 0x69, 0xfa, 0x35, 0x4d, 0x0e, 0x83, 0x2d, 0x0c, 0x60, 0x4f, 0x8f, 0xd3,\n\t0x24, 0x3b, 0x0d, 0x90, 0xc6, 0x79, 0x42, 0xbf, 0x27, 0x34, 0xe8, 0xe1, 0x21, 0x38, 0x33, 0x7a,\n\t0x72, 0x38, 0x9f, 0x26, 0x34, 0xe8, 0x6b, 0x36, 0x3d, 0xc9, 0xf2, 0xf9, 0xb7, 0x84, 0x06, 0x56,\n\t0x24, 0xc0, 0x59, 0x4d, 0xee, 0x25, 0x0c, 0x1b, 0x5e, 0xdf, 0x88, 0x73, 0xbe, 0x30, 0x8d, 0x68,\n\t0xdf, 0xe5, 0x75, 0x5a, 0xa6, 0x8b, 0x81, 0xc1, 0x12, 0xd5, 0xcd, 0xbb, 0xae, 0x82, 0x06, 0x77,\n\t0xda, 0xfb, 0xae, 0x80, 0x06, 0x6b, 0xad, 0x2a, 0x6b, 0x65, 0x2a, 0x38, 0xa0, 0x06, 0x47, 0xfb,\n\t0x00, 0x77, 0x63, 0x7f, 0x58, 0x31, 0xb4, 0x5e, 0xb1, 0x8d, 0x73, 0x88, 0xf6, 0xc0, 0x3b, 0x16,\n\t0x8d, 0x3a, 0xf9, 0xa1, 0x17, 0xd1, 0xe0, 0x57, 0x30, 0x68, 0x34, 0x20, 0xc8, 0x6c, 0xeb, 0xff,\n\t0x0d, 0xdb, 0xa2, 0xad, 0x23, 0x0a, 0xc0, 0xa7, 0x5c, 0xbf, 0x82, 0xf2, 0xa6, 0x2a, 0x65, 0xc3,\n\t0x27, 0xa7, 0xe0, 0x69, 0x43, 0xde, 0xfe, 0x1c, 0x4e, 0xc0, 0x6e, 0x0d, 0x78, 0xb4, 0x16, 0x73,\n\t0xef, 0xc6, 0xd1, 0x7a, 0x21, 0x1e, 0x66, 0x46, 0x5b, 0x5f, 0x30, 0xf8, 0xad, 0x63, 0xd2, 0x59,\n\t0x66, 0xe8, 0xcc, 0x6e, 0xd1, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x97, 0x2e, 0x7e, 0x05,\n\t0x04, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// SpanServiceClient is the client API for SpanService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype SpanServiceClient interface {\n\t// Report the provided spans to the collector. Analogous to the HTTP POST\n\t// /api/v2/spans endpoint. Spans are not required to be complete or belonging\n\t// to the same trace.\n\tReport(ctx context.Context, in *ListOfSpans, opts ...grpc.CallOption) (*ReportResponse, error)\n}\n\ntype spanServiceClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewSpanServiceClient(cc *grpc.ClientConn) SpanServiceClient {\n\treturn &spanServiceClient{cc}\n}\n\nfunc (c *spanServiceClient) Report(ctx context.Context, in *ListOfSpans, opts ...grpc.CallOption) (*ReportResponse, error) {\n\tout := new(ReportResponse)\n\terr := c.cc.Invoke(ctx, \"/zipkin.proto3.SpanService/Report\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// SpanServiceServer is the server API for SpanService service.\ntype SpanServiceServer interface {\n\t// Report the provided spans to the collector. Analogous to the HTTP POST\n\t// /api/v2/spans endpoint. Spans are not required to be complete or belonging\n\t// to the same trace.\n\tReport(context.Context, *ListOfSpans) (*ReportResponse, error)\n}\n\n// UnimplementedSpanServiceServer can be embedded to have forward compatible implementations.\ntype UnimplementedSpanServiceServer struct {\n}\n\nfunc (*UnimplementedSpanServiceServer) Report(ctx context.Context, req *ListOfSpans) (*ReportResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Report not implemented\")\n}\n\nfunc RegisterSpanServiceServer(s *grpc.Server, srv SpanServiceServer) {\n\ts.RegisterService(&_SpanService_serviceDesc, srv)\n}\n\nfunc _SpanService_Report_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListOfSpans)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(SpanServiceServer).Report(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/zipkin.proto3.SpanService/Report\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(SpanServiceServer).Report(ctx, req.(*ListOfSpans))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _SpanService_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"zipkin.proto3.SpanService\",\n\tHandlerType: (*SpanServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Report\",\n\t\t\tHandler:    _SpanService_Report_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"zipkin.proto\",\n}\n"
  },
  {
    "path": "internal/recoveryhandler/zap.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017-2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage recoveryhandler\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/gorilla/handlers\"\n\t\"go.uber.org/zap\"\n)\n\n// zapRecoveryWrapper wraps a zap logger into a gorilla RecoveryLogger\ntype zapRecoveryWrapper struct {\n\tlogger *zap.Logger\n}\n\n// Println logs an error message with the given fields\nfunc (z zapRecoveryWrapper) Println(args ...any) {\n\tz.logger.Error(fmt.Sprint(args...))\n}\n\n// NewRecoveryHandler returns an http.Handler that recovers on panics\nfunc NewRecoveryHandler(logger *zap.Logger, printStack bool) func(h http.Handler) http.Handler {\n\tzWrapper := zapRecoveryWrapper{logger}\n\treturn handlers.RecoveryHandler(handlers.RecoveryLogger(zWrapper), handlers.PrintRecoveryStack(printStack))\n}\n"
  },
  {
    "path": "internal/recoveryhandler/zap_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage recoveryhandler\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestNewRecoveryHandler(t *testing.T) {\n\tlogger, log := testutils.NewLogger()\n\n\thandlerFunc := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {\n\t\tpanic(\"Unexpected error!\")\n\t})\n\n\trecovery := NewRecoveryHandler(logger, false)(handlerFunc)\n\treq, err := http.NewRequest(http.MethodGet, \"/subdir/asdf\", http.NoBody)\n\trequire.NoError(t, err)\n\n\tres := httptest.NewRecorder()\n\trecovery.ServeHTTP(res, req)\n\tassert.Equal(t, http.StatusInternalServerError, res.Code)\n\tassert.Equal(t, map[string]string{\n\t\t\"level\": \"error\",\n\t\t\"msg\":   \"Unexpected error!\",\n\t}, log.JSONLine(0))\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/safeexpvar/safeexpvar.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage safeexpvar\n\nimport (\n\t\"expvar\"\n)\n\nfunc SetInt(name string, value int64) {\n\tv := expvar.Get(name)\n\tif v == nil {\n\t\tv = expvar.NewInt(name)\n\t}\n\tv.(*expvar.Int).Set(value)\n}\n"
  },
  {
    "path": "internal/safeexpvar/safeexpvar_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage safeexpvar\n\nimport (\n\t\"expvar\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestSetInt(t *testing.T) {\n\t// Test with a new variable\n\tname := \"metrics-test-1\"\n\tvalue := int64(42)\n\n\tSetInt(name, value)\n\n\t// Retrieve the variable and check its value\n\tv := expvar.Get(name)\n\tassert.NotNil(t, v, \"expected variable %s to be created\", name)\n\texpInt, ok := v.(*expvar.Int)\n\trequire.True(t, ok, \"expected variable %s to be of type *expvar.Int\", name)\n\tassert.Equal(t, value, expInt.Value())\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/grpc/grpc_handler.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/sampling/samplingstrategy\"\n)\n\n// Handler is sampling strategy handler for gRPC.\ntype Handler struct {\n\tsamplingProvider samplingstrategy.Provider\n}\n\n// NewHandler creates a handler that controls sampling strategies for services.\nfunc NewHandler(provider samplingstrategy.Provider) Handler {\n\treturn Handler{\n\t\tsamplingProvider: provider,\n\t}\n}\n\n// GetSamplingStrategy returns sampling decision from store.\nfunc (s Handler) GetSamplingStrategy(ctx context.Context, param *api_v2.SamplingStrategyParameters) (*api_v2.SamplingStrategyResponse, error) {\n\treturn s.samplingProvider.GetSamplingStrategy(ctx, param.GetServiceName())\n}\n"
  },
  {
    "path": "internal/sampling/grpc/grpc_handler_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype mockSamplingStore struct{}\n\nfunc (mockSamplingStore) GetSamplingStrategy(_ context.Context, serviceName string) (*api_v2.SamplingStrategyResponse, error) {\n\tswitch serviceName {\n\tcase \"error\":\n\t\treturn nil, errors.New(\"some error\")\n\tcase \"nil\":\n\t\treturn nil, nil\n\tdefault:\n\t\treturn &api_v2.SamplingStrategyResponse{StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC}, nil\n\t}\n}\n\nfunc (mockSamplingStore) Close() error {\n\treturn nil\n}\n\nfunc TestNewGRPCHandler(t *testing.T) {\n\ttests := []struct {\n\t\treq  *api_v2.SamplingStrategyParameters\n\t\tresp *api_v2.SamplingStrategyResponse\n\t\terr  string\n\t}{\n\t\t{req: &api_v2.SamplingStrategyParameters{ServiceName: \"error\"}, err: \"some error\"},\n\t\t{req: &api_v2.SamplingStrategyParameters{ServiceName: \"nil\"}, resp: nil},\n\t\t{req: &api_v2.SamplingStrategyParameters{ServiceName: \"foo\"}, resp: &api_v2.SamplingStrategyResponse{StrategyType: api_v2.SamplingStrategyType_PROBABILISTIC}},\n\t}\n\th := NewHandler(mockSamplingStore{})\n\tfor _, test := range tests {\n\t\tresp, err := h.GetSamplingStrategy(context.Background(), test.req)\n\t\tif test.err != \"\" {\n\t\t\trequire.EqualError(t, err, test.err)\n\t\t\trequire.Nil(t, resp)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.resp, resp)\n\t\t}\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/http/cfgmgr.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage http\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/sampling/samplingstrategy\"\n)\n\n// ConfigManager implements ClientConfigManager.\ntype ConfigManager struct {\n\tSamplingProvider samplingstrategy.Provider\n}\n\n// GetSamplingStrategy implements ClientConfigManager.GetSamplingStrategy.\nfunc (c *ConfigManager) GetSamplingStrategy(ctx context.Context, serviceName string) (*api_v2.SamplingStrategyResponse, error) {\n\treturn c.SamplingProvider.GetSamplingStrategy(ctx, serviceName)\n}\n"
  },
  {
    "path": "internal/sampling/http/cfgmgr_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage http\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n)\n\ntype mockSamplingProvider struct {\n\tsamplingResponse *api_v2.SamplingStrategyResponse\n}\n\nfunc (m *mockSamplingProvider) GetSamplingStrategy(context.Context, string /* serviceName */) (*api_v2.SamplingStrategyResponse, error) {\n\tif m.samplingResponse == nil {\n\t\treturn nil, errors.New(\"no mock response provided\")\n\t}\n\treturn m.samplingResponse, nil\n}\n\nfunc (*mockSamplingProvider) Close() error {\n\treturn nil\n}\n\nfunc TestConfigManager(t *testing.T) {\n\tmgr := &ConfigManager{\n\t\tSamplingProvider: &mockSamplingProvider{\n\t\t\tsamplingResponse: &api_v2.SamplingStrategyResponse{},\n\t\t},\n\t}\n\tt.Run(\"GetSamplingStrategy\", func(t *testing.T) {\n\t\tr, err := mgr.GetSamplingStrategy(context.Background(), \"foo\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, api_v2.SamplingStrategyResponse{}, *r)\n\t})\n}\n"
  },
  {
    "path": "internal/sampling/http/handler.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage http\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tp2json \"github.com/jaegertracing/jaeger/internal/uimodel/converter/v1/json\"\n)\n\nconst mimeTypeApplicationJSON = \"application/json\"\n\nvar errBadRequest = errors.New(\"bad request\")\n\ntype ClientConfigManager interface {\n\tGetSamplingStrategy(ctx context.Context, serviceName string) (*api_v2.SamplingStrategyResponse, error)\n}\n\n// HandlerParams contains parameters that must be passed to NewHTTPHandler.\ntype HandlerParams struct {\n\tConfigManager  ClientConfigManager // required\n\tMetricsFactory metrics.Factory     // required\n}\n\n// Handler implements endpoints for used by Jaeger clients to retrieve client configuration,\n// such as sampling strategies.\ntype Handler struct {\n\tparams  HandlerParams\n\tmetrics struct {\n\t\t// Number of good sampling requests\n\t\tSamplingRequestSuccess metrics.Counter `metric:\"http-server.requests\" tags:\"type=sampling\"`\n\n\t\t// Number of bad requests (400s)\n\t\tBadRequest metrics.Counter `metric:\"http-server.errors\" tags:\"status=4xx,source=all\"`\n\n\t\t// Number of collector proxy failures\n\t\tCollectorProxyFailures metrics.Counter `metric:\"http-server.errors\" tags:\"status=5xx,source=collector-proxy\"`\n\n\t\t// Number of bad responses due to proto conversion\n\t\tBadProtoFailures metrics.Counter `metric:\"http-server.errors\" tags:\"status=5xx,source=proto\"`\n\n\t\t// Number of failed response writes from http server\n\t\tWriteFailures metrics.Counter `metric:\"http-server.errors\" tags:\"status=5xx,source=write\"`\n\t}\n}\n\n// NewHandler creates new HTTPHandler.\nfunc NewHandler(params HandlerParams) *Handler {\n\thandler := &Handler{params: params}\n\tmetrics.MustInit(&handler.metrics, params.MetricsFactory, nil)\n\treturn handler\n}\n\n// RegisterRoutes registers configuration handlers with HTTP Router.\nfunc (h *Handler) RegisterRoutes(router *http.ServeMux) {\n\trouter.HandleFunc(\n\t\t\"/\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\th.serveSamplingHTTP(w, r, h.encodeProto)\n\t\t},\n\t)\n}\n\nfunc (h *Handler) serviceFromRequest(w http.ResponseWriter, r *http.Request) (string, error) {\n\tservices := r.URL.Query()[\"service\"]\n\tif len(services) != 1 {\n\t\th.metrics.BadRequest.Inc(1)\n\t\thttp.Error(w, \"'service' parameter must be provided once\", http.StatusBadRequest)\n\t\treturn \"\", errBadRequest\n\t}\n\treturn services[0], nil\n}\n\nfunc (h *Handler) writeJSON(w http.ResponseWriter, jsonData []byte) error {\n\tw.Header().Add(\"Content-Type\", mimeTypeApplicationJSON)\n\tif _, err := w.Write(jsonData); err != nil {\n\t\th.metrics.WriteFailures.Inc(1)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (h *Handler) serveSamplingHTTP(\n\tw http.ResponseWriter,\n\tr *http.Request,\n\tencoder func(strategy *api_v2.SamplingStrategyResponse) ([]byte, error),\n) {\n\tservice, err := h.serviceFromRequest(w, r)\n\tif err != nil {\n\t\treturn\n\t}\n\tresp, err := h.params.ConfigManager.GetSamplingStrategy(r.Context(), service)\n\tif err != nil {\n\t\th.metrics.CollectorProxyFailures.Inc(1)\n\t\thttp.Error(w, fmt.Sprintf(\"collector error: %+v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tjsonBytes, err := encoder(resp)\n\tif err != nil {\n\t\thttp.Error(w, \"cannot marshall to JSON\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tif err = h.writeJSON(w, jsonBytes); err != nil {\n\t\treturn\n\t}\n}\n\nfunc (h *Handler) encodeProto(strategy *api_v2.SamplingStrategyResponse) ([]byte, error) {\n\tstr, err := p2json.SamplingStrategyResponseToJSON(strategy)\n\tif err != nil {\n\t\th.metrics.BadProtoFailures.Inc(1)\n\t\treturn nil, fmt.Errorf(\"SamplingStrategyResponseToJSON failed: %w\", err)\n\t}\n\th.metrics.SamplingRequestSuccess.Inc(1)\n\treturn []byte(str), nil\n}\n"
  },
  {
    "path": "internal/sampling/http/handler_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage http\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n)\n\n// parseSamplingResponse parses a JSON sampling strategy response\n// using the same jsonpb.Unmarshal logic as the OTel Jaeger Remote Sampler SDK.\n// See: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/samplers/jaegerremote/sampler_remote.go\nfunc parseSamplingResponse(t *testing.T, body []byte) *api_v2.SamplingStrategyResponse {\n\tt.Helper()\n\tstrategy := new(api_v2.SamplingStrategyResponse)\n\trequire.NoError(t, jsonpb.Unmarshal(bytes.NewReader(body), strategy))\n\treturn strategy\n}\n\ntype testServer struct {\n\tmetricsFactory   *metricstest.Factory\n\tsamplingProvider *mockSamplingProvider\n\tserver           *httptest.Server\n\thandler          *Handler\n}\n\nfunc withServer(\n\tmockSamplingResponse *api_v2.SamplingStrategyResponse,\n\ttestFn func(server *testServer),\n) {\n\tmetricsFactory := metricstest.NewFactory(0)\n\tsamplingProvider := &mockSamplingProvider{samplingResponse: mockSamplingResponse}\n\tcfgMgr := &ConfigManager{\n\t\tSamplingProvider: samplingProvider,\n\t}\n\thandler := NewHandler(HandlerParams{\n\t\tConfigManager:  cfgMgr,\n\t\tMetricsFactory: metricsFactory,\n\t})\n\n\thttpMux := http.NewServeMux()\n\thandler.RegisterRoutes(httpMux)\n\tserver := httptest.NewServer(httpMux)\n\n\tdefer server.Close()\n\ttestFn(&testServer{\n\t\tmetricsFactory:   metricsFactory,\n\t\tsamplingProvider: samplingProvider,\n\t\tserver:           server,\n\t\thandler:          handler,\n\t})\n}\n\nfunc TestHTTPHandler(t *testing.T) {\n\twithServer(rateLimiting(42), func(ts *testServer) {\n\t\tresp, err := http.Get(ts.server.URL + \"/?service=Y\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, resp.Body.Close())\n\t\tassert.JSONEq(t,\n\t\t\t`{\"strategyType\":\"RATE_LIMITING\",\"rateLimitingSampling\":{\"maxTracesPerSecond\":42}}`,\n\t\t\tstring(body))\n\t\tobjResp := parseSamplingResponse(t, body)\n\t\tassert.Equal(t, ts.samplingProvider.samplingResponse, objResp)\n\n\t\t// handler must emit metrics\n\t\tts.metricsFactory.AssertCounterMetrics(t,\n\t\t\tmetricstest.ExpectedMetric{Name: \"http-server.requests\", Tags: map[string]string{\"type\": \"sampling\"}, Value: 1})\n\t})\n}\n\n// TestOTelSDKCompatibility verifies that the response from\n// the RegisterRoutes endpoint can be parsed by the same jsonpb.Unmarshal\n// logic used in the OpenTelemetry Jaeger Remote Sampler SDK:\n// https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/samplers/jaegerremote/sampler_remote.go\n//\n// The SDK's Parse function does:\n//\n//\tstrategy := new(jaeger_api_v2.SamplingStrategyResponse)\n//\tif err := jsonpb.Unmarshal(bytes.NewReader(response), strategy); err != nil { ... }\n//\n// Gogo's jsonpb module can parse both string-based and numeric enum formats.\n// Cf. https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3184\nfunc TestOTelSDKCompatibility(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tresponse *api_v2.SamplingStrategyResponse\n\t}{\n\t\t{\n\t\t\tname:     \"rate limiting strategy\",\n\t\t\tresponse: rateLimiting(42),\n\t\t},\n\t\t{\n\t\t\tname:     \"probabilistic strategy\",\n\t\t\tresponse: probabilistic(0.5),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\twithServer(test.response, func(ts *testServer) {\n\t\t\t\tresp, err := http.Get(ts.server.URL + \"/?service=Y\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NoError(t, resp.Body.Close())\n\n\t\t\t\t// Parse the response the same way as the OTel Jaeger Remote Sampler SDK.\n\t\t\t\t// See: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/samplers/jaegerremote/sampler_remote.go\n\t\t\t\tobjResp := parseSamplingResponse(t, body)\n\t\t\t\tassert.Equal(t,\n\t\t\t\t\ttest.response.GetStrategyType(),\n\t\t\t\t\tobjResp.GetStrategyType())\n\t\t\t\t// even though one of these strategies is nil, the generated code\n\t\t\t\t// still allows to call next method on it and return default value.\n\t\t\t\tassert.InDelta(t,\n\t\t\t\t\ttest.response.GetProbabilisticSampling().GetSamplingRate(),\n\t\t\t\t\tobjResp.GetProbabilisticSampling().GetSamplingRate(),\n\t\t\t\t\t0)\n\t\t\t\tassert.Equal(t,\n\t\t\t\t\ttest.response.GetRateLimitingSampling().GetMaxTracesPerSecond(),\n\t\t\t\t\tobjResp.GetRateLimitingSampling().GetMaxTracesPerSecond())\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestHTTPHandlerErrors(t *testing.T) {\n\ttestCases := []struct {\n\t\tdescription          string\n\t\tmockSamplingResponse *api_v2.SamplingStrategyResponse\n\t\turl                  string\n\t\tstatusCode           int\n\t\tbody                 string\n\t\tmetrics              []metricstest.ExpectedMetric\n\t}{\n\t\t{\n\t\t\tdescription: \"no service name\",\n\t\t\turl:         \"\",\n\t\t\tstatusCode:  http.StatusBadRequest,\n\t\t\tbody:        \"'service' parameter must be provided once\\n\",\n\t\t\tmetrics: []metricstest.ExpectedMetric{\n\t\t\t\t{Name: \"http-server.errors\", Tags: map[string]string{\"source\": \"all\", \"status\": \"4xx\"}, Value: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"sampling endpoint too many service names\",\n\t\t\turl:         \"?service=Y&service=Y\",\n\t\t\tstatusCode:  http.StatusBadRequest,\n\t\t\tbody:        \"'service' parameter must be provided once\\n\",\n\t\t\tmetrics: []metricstest.ExpectedMetric{\n\t\t\t\t{Name: \"http-server.errors\", Tags: map[string]string{\"source\": \"all\", \"status\": \"4xx\"}, Value: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"sampler collector error\",\n\t\t\turl:         \"?service=Y\",\n\t\t\tstatusCode:  http.StatusInternalServerError,\n\t\t\tbody:        \"collector error: no mock response provided\\n\",\n\t\t\tmetrics: []metricstest.ExpectedMetric{\n\t\t\t\t{Name: \"http-server.errors\", Tags: map[string]string{\"source\": \"collector-proxy\", \"status\": \"5xx\"}, Value: 1},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.description, func(t *testing.T) {\n\t\t\twithServer(testCase.mockSamplingResponse, func(ts *testServer) {\n\t\t\t\tresp, err := http.Get(ts.server.URL + testCase.url)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer resp.Body.Close()\n\t\t\t\tassert.Equal(t, testCase.statusCode, resp.StatusCode)\n\t\t\t\tif testCase.body != \"\" {\n\t\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.Equal(t, testCase.body, string(body))\n\t\t\t\t}\n\n\t\t\t\tif len(testCase.metrics) > 0 {\n\t\t\t\t\tts.metricsFactory.AssertCounterMetrics(t, testCase.metrics...)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\tt.Run(\"failure to write a response\", func(t *testing.T) {\n\t\twithServer(probabilistic(0.001), func(ts *testServer) {\n\t\t\thandler := ts.handler\n\n\t\t\treq := httptest.NewRequest(http.MethodGet, \"http://localhost:80/?service=X\", http.NoBody)\n\t\t\tw := &mockWriter{header: make(http.Header)}\n\t\t\thandler.serveSamplingHTTP(w, req, handler.encodeProto)\n\n\t\t\tts.metricsFactory.AssertCounterMetrics(t,\n\t\t\t\tmetricstest.ExpectedMetric{Name: \"http-server.errors\", Tags: map[string]string{\"source\": \"write\", \"status\": \"5xx\"}, Value: 1})\n\t\t})\n\t})\n}\n\nfunc TestEncodeErrors(t *testing.T) {\n\twithServer(nil, func(server *testServer) {\n\t\t_, err := server.handler.encodeProto(nil)\n\t\trequire.ErrorContains(t, err, \"SamplingStrategyResponseToJSON failed\")\n\t\tserver.metricsFactory.AssertCounterMetrics(t, []metricstest.ExpectedMetric{\n\t\t\t{Name: \"http-server.errors\", Tags: map[string]string{\"source\": \"proto\", \"status\": \"5xx\"}, Value: 1},\n\t\t}...)\n\t})\n}\n\nfunc rateLimiting(rate int32) *api_v2.SamplingStrategyResponse {\n\treturn &api_v2.SamplingStrategyResponse{\n\t\tStrategyType: api_v2.SamplingStrategyType_RATE_LIMITING,\n\t\tRateLimitingSampling: &api_v2.RateLimitingSamplingStrategy{\n\t\t\tMaxTracesPerSecond: rate,\n\t\t},\n\t}\n}\n\nfunc probabilistic(probability float64) *api_v2.SamplingStrategyResponse {\n\treturn &api_v2.SamplingStrategyResponse{\n\t\tStrategyType: api_v2.SamplingStrategyType_PROBABILISTIC,\n\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\tSamplingRate: probability,\n\t\t},\n\t}\n}\n\ntype mockWriter struct {\n\theader http.Header\n}\n\nfunc (w *mockWriter) Header() http.Header {\n\treturn w.header\n}\n\nfunc (*mockWriter) Write([]byte) (int, error) {\n\treturn 0, errors.New(\"write error\")\n}\n\nfunc (*mockWriter) WriteHeader(int) {}\n"
  },
  {
    "path": "internal/sampling/http/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage http\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/README.md",
    "content": "# Adaptive Sampling\n\nAdaptive sampling works in Jaeger collector by observing the spans received from services and recalculating sampling probabilities for each service/endpoint combination to ensure that the volume of collected traces matches the desired target of traces per second. When a new service or endpoint is detected, it is initially sampled with \"initial-sampling-probability\" until enough data is collected to calculate the rate appropriate for the traffic going through the endpoint.\n\nAdaptive sampling requires a storage backend to store the observed traffic data and computed probabilities. At the moment memory (for all-in-one deployment), cassandra, badger, elasticsearch and opensearch are supported as sampling storage backends.\n\nNote: adaptive sampling in Jaeger backend *does not actually do the sampling*. The sampling is performed by OTEL SDKs, and sampling decisions are propagated through trace context. The job of Adaptive Sampling is to dynamically calculate sampling probabilities and expose them as sampling strategies via Jaeger's Remote Sampling protocol.\n\nReferences:\n  * [Documentation](https://www.jaegertracing.io/docs/latest/sampling/#adaptive-sampling)\n  * [Blog post](https://medium.com/jaegertracing/adaptive-sampling-in-jaeger-50f336f4334)\n\n## Implementation details\n\nThere are three main components of the Adaptive Sampling: Aggregator, Post-aggregator (could use a better name), and Provider.\n\n### Aggregator\n\n*Aggregator* is a component that runs in the ingestion pipeline (e.g. as a trace processor in OTEL Collector). It looks at all spans passing through that instance of the collector and looks for root spans. Each root span indicates a new trace being generated, so the aggregation aggregates the count of those traces (grouped by service name and span name) and periodically flushes those aggregates (called \"throughput\") to storage.\n\n### Post-aggregator\n\n*Post-aggregator* is the main logic responsible for _adaptive_ part of this sampling strategy implementation. Its main job is to load all throughput from storage (because multiple instances of collector could've written different aggregates), aggregate it into a final output, and compute the desired sampling probabilities, which are also written into storage.\n\nIn a typical production usage Jaeger deployment consists of many collectors. Each collector runs an independent aggregator, because they do not require coordination as long as there is a shared storage. Each collector also runs post-aggregator, however only one of those should be combining the output of all aggregators and producing the final sampling probabilities. This is achieved by using a simple leader-follower election with the help of the storage backend. The leader post-aggregator does the main job of the computation, while the follower-aggregators are only loading the throughput from storage and aggregate it in memory, so that each of them is ready to assume the role of the leader if needed, but they do not compute the probabilities or write them back into storage.\n\n### Provider\n\n*Provider* is responsible for providing the sampling strategy to the SDKs when they poll the `/sampling` endpoint. It periodically reads the computed sampling probabilities from storage and translates them into sampling strategy output expected by the Jaeger Remote Sampling protocol.\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/aggregator.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\tspanmodel \"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/hostname\"\n\t\"github.com/jaegertracing/jaeger/internal/leaderelection\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/sampling/samplingstrategy\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nconst (\n\tmaxProbabilities = 10\n)\n\n// aggregator is a kind of trace processor that watches for root spans\n// and calculates how many traces per service / per endpoint are being\n// produced. It periodically flushes these stats (\"throughput\") to storage.\n//\n// It also invokes PostAggregator which actually computes adaptive sampling\n// probabilities based on the observed throughput.\ntype aggregator struct {\n\tsync.Mutex\n\n\toperationsCounter   metrics.Counter\n\tservicesCounter     metrics.Counter\n\tcurrentThroughput   serviceOperationThroughput\n\tpostAggregator      *PostAggregator\n\taggregationInterval time.Duration\n\tstorage             samplingstore.Store\n\tstop                chan struct{}\n\tbgFinished          sync.WaitGroup\n}\n\n// NewAggregator creates a throughput aggregator that simply emits metrics\n// about the number of operations seen over the aggregationInterval.\nfunc NewAggregator(options Options, logger *zap.Logger, metricsFactory metrics.Factory, participant leaderelection.ElectionParticipant, store samplingstore.Store) (samplingstrategy.Aggregator, error) {\n\thostId, err := hostname.AsIdentifier()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogger.Info(\"Using unique participantName in adaptive sampling\", zap.String(\"participantName\", hostId))\n\n\tpostAggregator, err := newPostAggregator(options, hostId, store, participant, metricsFactory, logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &aggregator{\n\t\toperationsCounter:   metricsFactory.Counter(metrics.Options{Name: \"sampling_operations\"}),\n\t\tservicesCounter:     metricsFactory.Counter(metrics.Options{Name: \"sampling_services\"}),\n\t\tcurrentThroughput:   make(serviceOperationThroughput),\n\t\taggregationInterval: options.CalculationInterval,\n\t\tpostAggregator:      postAggregator,\n\t\tstorage:             store,\n\t\tstop:                make(chan struct{}),\n\t}, nil\n}\n\nfunc (a *aggregator) runAggregationLoop() {\n\tticker := time.NewTicker(a.aggregationInterval)\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\ta.Lock()\n\t\t\ta.saveThroughput()\n\t\t\ta.currentThroughput = make(serviceOperationThroughput)\n\t\t\ta.postAggregator.runCalculation()\n\t\t\ta.Unlock()\n\t\tcase <-a.stop:\n\t\t\tticker.Stop()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (a *aggregator) saveThroughput() {\n\ttotalOperations := 0\n\tvar throughputSlice []*model.Throughput\n\tfor _, opThroughput := range a.currentThroughput {\n\t\ttotalOperations += len(opThroughput)\n\t\tfor _, throughput := range opThroughput {\n\t\t\tthroughputSlice = append(throughputSlice, throughput)\n\t\t}\n\t}\n\ta.operationsCounter.Inc(int64(totalOperations))\n\ta.servicesCounter.Inc(int64(len(a.currentThroughput)))\n\ta.storage.InsertThroughput(throughputSlice)\n}\n\nfunc (a *aggregator) RecordThroughput(service, operation string, samplerType spanmodel.SamplerType, probability float64) {\n\ta.Lock()\n\tdefer a.Unlock()\n\tif _, ok := a.currentThroughput[service]; !ok {\n\t\ta.currentThroughput[service] = make(map[string]*model.Throughput)\n\t}\n\tthroughput, ok := a.currentThroughput[service][operation]\n\tif !ok {\n\t\tthroughput = &model.Throughput{\n\t\t\tService:       service,\n\t\t\tOperation:     operation,\n\t\t\tProbabilities: make(map[string]struct{}),\n\t\t}\n\t\ta.currentThroughput[service][operation] = throughput\n\t}\n\tprobStr := TruncateFloat(probability)\n\tif len(throughput.Probabilities) != maxProbabilities {\n\t\tthroughput.Probabilities[probStr] = struct{}{}\n\t}\n\t// Only if we see probabilistically sampled root spans do we increment the throughput counter,\n\t// for lowerbound sampled spans, we don't increment at all but we still save a count of 0 as\n\t// the throughput so that the adaptive sampling processor is made aware of the endpoint.\n\tif samplerType == spanmodel.SamplerTypeProbabilistic {\n\t\tthroughput.Count++\n\t}\n}\n\nfunc (a *aggregator) Start() {\n\ta.postAggregator.Start()\n\n\ta.bgFinished.Go(func() {\n\t\ta.runAggregationLoop()\n\t})\n}\n\nfunc (a *aggregator) Close() error {\n\tclose(a.stop)\n\ta.bgFinished.Wait()\n\treturn nil\n}\n\nfunc (a *aggregator) HandleRootSpan(span *spanmodel.Span) {\n\t// simply checking parentId to determine if a span is a root span is not sufficient. However,\n\t// we can be sure that only a root span will have sampler tags.\n\tif span.ParentSpanID() != spanmodel.NewSpanID(0) {\n\t\treturn\n\t}\n\tservice := span.Process.ServiceName\n\tif service == \"\" || span.OperationName == \"\" {\n\t\treturn\n\t}\n\tsamplerType, samplerParam := getSamplerParams(span, a.postAggregator.logger)\n\tif samplerType == spanmodel.SamplerTypeUnrecognized {\n\t\treturn\n\t}\n\ta.RecordThroughput(service, span.OperationName, samplerType, samplerParam)\n}\n\n// GetSamplerParams returns the sampler.type and sampler.param value if they are valid.\nfunc getSamplerParams(s *spanmodel.Span, logger *zap.Logger) (spanmodel.SamplerType, float64) {\n\tsamplerType := s.GetSamplerType()\n\tif samplerType == spanmodel.SamplerTypeUnrecognized {\n\t\treturn spanmodel.SamplerTypeUnrecognized, 0\n\t}\n\ttag, ok := spanmodel.KeyValues(s.Tags).FindByKey(spanmodel.SamplerParamKey)\n\tif !ok {\n\t\treturn spanmodel.SamplerTypeUnrecognized, 0\n\t}\n\tsamplerParam, err := samplerParamToFloat(tag)\n\tif err != nil {\n\t\tlogger.\n\t\t\tWith(zap.String(\"traceID\", s.TraceID.String())).\n\t\t\tWith(zap.String(\"spanID\", s.SpanID.String())).\n\t\t\tWarn(\"sampler.param tag is not a number\", zap.Any(\"tag\", tag))\n\t\treturn spanmodel.SamplerTypeUnrecognized, 0\n\t}\n\treturn samplerType, samplerParam\n}\n\nfunc samplerParamToFloat(samplerParamTag spanmodel.KeyValue) (float64, error) {\n\t// The param could be represented as a string, an int, or a float\n\tswitch samplerParamTag.VType {\n\tcase spanmodel.Float64Type:\n\t\treturn samplerParamTag.Float64(), nil\n\tcase spanmodel.Int64Type:\n\t\treturn float64(samplerParamTag.Int64()), nil\n\tdefault:\n\t\treturn strconv.ParseFloat(samplerParamTag.AsString(), 64)\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/aggregator_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tepmocks \"github.com/jaegertracing/jaeger/internal/leaderelection/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/mocks\"\n)\n\nfunc TestAggregator(t *testing.T) {\n\tt.Skip(\"Skipping flaky unit test\")\n\tmetricsFactory := metricstest.NewFactory(0)\n\n\tmockStorage := &mocks.Store{}\n\tmockStorage.On(\"InsertThroughput\", mock.AnythingOfType(\"[]*model.Throughput\")).Return(nil)\n\tmockEP := &epmocks.ElectionParticipant{}\n\tmockEP.On(\"Start\").Return(nil)\n\tmockEP.On(\"Close\").Return(nil)\n\tmockEP.On(\"IsLeader\").Return(true)\n\ttestOpts := Options{\n\t\tCalculationInterval:   1 * time.Second,\n\t\tAggregationBuckets:    1,\n\t\tBucketsForCalculation: 1,\n\t}\n\tlogger := zap.NewNop()\n\n\ta, err := NewAggregator(testOpts, logger, metricsFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\ta.RecordThroughput(\"A\", http.MethodGet, model.SamplerTypeProbabilistic, 0.001)\n\ta.RecordThroughput(\"B\", http.MethodPost, model.SamplerTypeProbabilistic, 0.001)\n\ta.RecordThroughput(\"C\", http.MethodGet, model.SamplerTypeProbabilistic, 0.001)\n\ta.RecordThroughput(\"A\", http.MethodPost, model.SamplerTypeProbabilistic, 0.001)\n\ta.RecordThroughput(\"A\", http.MethodGet, model.SamplerTypeProbabilistic, 0.001)\n\ta.RecordThroughput(\"A\", http.MethodGet, model.SamplerTypeLowerBound, 0.001)\n\n\ta.Start()\n\tdefer a.Close()\n\tfor range 10000 {\n\t\tcounters, _ := metricsFactory.Snapshot()\n\t\tif _, ok := counters[\"sampling_operations\"]; ok {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(1 * time.Millisecond)\n\t}\n\n\tmetricsFactory.AssertCounterMetrics(t, []metricstest.ExpectedMetric{\n\t\t{Name: \"sampling_operations\", Value: 4},\n\t\t{Name: \"sampling_services\", Value: 3},\n\t}...)\n}\n\nfunc TestIncrementThroughput(t *testing.T) {\n\tmetricsFactory := metricstest.NewFactory(0)\n\tmockStorage := &mocks.Store{}\n\tmockEP := &epmocks.ElectionParticipant{}\n\ttestOpts := Options{\n\t\tCalculationInterval:   1 * time.Second,\n\t\tAggregationBuckets:    1,\n\t\tBucketsForCalculation: 1,\n\t}\n\tlogger := zap.NewNop()\n\ta, err := NewAggregator(testOpts, logger, metricsFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\t// 20 different probabilities\n\tfor i := range 20 {\n\t\ta.RecordThroughput(\"A\", http.MethodGet, model.SamplerTypeProbabilistic, 0.001*float64(i))\n\t}\n\tassert.Len(t, a.(*aggregator).currentThroughput[\"A\"][http.MethodGet].Probabilities, 10)\n\n\ta, err = NewAggregator(testOpts, logger, metricsFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\t// 20 of the same probabilities\n\tfor range 20 {\n\t\ta.RecordThroughput(\"A\", http.MethodGet, model.SamplerTypeProbabilistic, 0.001)\n\t}\n\tassert.Len(t, a.(*aggregator).currentThroughput[\"A\"][http.MethodGet].Probabilities, 1)\n}\n\nfunc TestLowerboundThroughput(t *testing.T) {\n\tmetricsFactory := metricstest.NewFactory(0)\n\tmockStorage := &mocks.Store{}\n\tmockEP := &epmocks.ElectionParticipant{}\n\ttestOpts := Options{\n\t\tCalculationInterval:   1 * time.Second,\n\t\tAggregationBuckets:    1,\n\t\tBucketsForCalculation: 1,\n\t}\n\tlogger := zap.NewNop()\n\n\ta, err := NewAggregator(testOpts, logger, metricsFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\ta.RecordThroughput(\"A\", http.MethodGet, model.SamplerTypeLowerBound, 0.001)\n\tassert.EqualValues(t, 0, a.(*aggregator).currentThroughput[\"A\"][http.MethodGet].Count)\n\tassert.Empty(t, a.(*aggregator).currentThroughput[\"A\"][http.MethodGet].Probabilities[\"0.001000\"])\n}\n\nfunc TestRecordThroughput(t *testing.T) {\n\tmetricsFactory := metricstest.NewFactory(0)\n\tmockStorage := &mocks.Store{}\n\tmockEP := &epmocks.ElectionParticipant{}\n\ttestOpts := Options{\n\t\tCalculationInterval:   1 * time.Second,\n\t\tAggregationBuckets:    1,\n\t\tBucketsForCalculation: 1,\n\t}\n\tlogger := zap.NewNop()\n\ta, err := NewAggregator(testOpts, logger, metricsFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\n\t// Testing non-root span\n\tspan := &model.Span{References: []model.SpanRef{{SpanID: model.NewSpanID(1), RefType: model.ChildOf}}}\n\ta.HandleRootSpan(span)\n\trequire.Empty(t, a.(*aggregator).currentThroughput)\n\n\t// Testing span with service name but no operation\n\tspan.References = []model.SpanRef{}\n\tspan.Process = &model.Process{\n\t\tServiceName: \"A\",\n\t}\n\ta.HandleRootSpan(span)\n\trequire.Empty(t, a.(*aggregator).currentThroughput)\n\n\t// Testing span with service name and operation but no probabilistic sampling tags\n\tspan.OperationName = http.MethodGet\n\ta.HandleRootSpan(span)\n\trequire.Empty(t, a.(*aggregator).currentThroughput)\n\n\t// Testing span with service name, operation, and probabilistic sampling tags\n\tspan.Tags = model.KeyValues{\n\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\tmodel.String(\"sampler.param\", \"0.001\"),\n\t}\n\ta.HandleRootSpan(span)\n\tassert.EqualValues(t, 1, a.(*aggregator).currentThroughput[\"A\"][http.MethodGet].Count)\n}\n\nfunc TestRecordThroughputFunc(t *testing.T) {\n\tmetricsFactory := metricstest.NewFactory(0)\n\tmockStorage := &mocks.Store{}\n\tmockEP := &epmocks.ElectionParticipant{}\n\tlogger := zap.NewNop()\n\ttestOpts := Options{\n\t\tCalculationInterval:   1 * time.Second,\n\t\tAggregationBuckets:    1,\n\t\tBucketsForCalculation: 1,\n\t}\n\n\ta, err := NewAggregator(testOpts, logger, metricsFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\n\t// Testing non-root span\n\tspan := &model.Span{References: []model.SpanRef{{SpanID: model.NewSpanID(1), RefType: model.ChildOf}}}\n\ta.HandleRootSpan(span)\n\trequire.Empty(t, a.(*aggregator).currentThroughput)\n\n\t// Testing span with service name but no operation\n\tspan.References = []model.SpanRef{}\n\tspan.Process = &model.Process{\n\t\tServiceName: \"A\",\n\t}\n\ta.HandleRootSpan(span)\n\trequire.Empty(t, a.(*aggregator).currentThroughput)\n\n\t// Testing span with service name and operation but no probabilistic sampling tags\n\tspan.OperationName = http.MethodGet\n\ta.HandleRootSpan(span)\n\trequire.Empty(t, a.(*aggregator).currentThroughput)\n\n\t// Testing span with service name, operation, and probabilistic sampling tags\n\tspan.Tags = model.KeyValues{\n\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\tmodel.String(\"sampler.param\", \"0.001\"),\n\t}\n\ta.HandleRootSpan(span)\n\tassert.EqualValues(t, 1, a.(*aggregator).currentThroughput[\"A\"][http.MethodGet].Count)\n}\n\nfunc TestGetSamplerParams(t *testing.T) {\n\tlogger := zap.NewNop()\n\n\ttests := []struct {\n\t\ttags          model.KeyValues\n\t\texpectedType  model.SamplerType\n\t\texpectedParam float64\n\t}{\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\t\t\tmodel.String(\"sampler.param\", \"1e-05\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeProbabilistic,\n\t\t\texpectedParam: 0.00001,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\t\t\tmodel.Float64(\"sampler.param\", 0.10404450002098709),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeProbabilistic,\n\t\t\texpectedParam: 0.10404450002098709,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\t\t\tmodel.String(\"sampler.param\", \"0.10404450002098709\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeProbabilistic,\n\t\t\texpectedParam: 0.10404450002098709,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\t\t\tmodel.Int64(\"sampler.param\", 1),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeProbabilistic,\n\t\t\texpectedParam: 1.0,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"ratelimiting\"),\n\t\t\t\tmodel.String(\"sampler.param\", \"1\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeRateLimiting,\n\t\t\texpectedParam: 1,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.Float64(\"sampler.type\", 1.5),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeUnrecognized,\n\t\t\texpectedParam: 0,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"probabilistic\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeUnrecognized,\n\t\t\texpectedParam: 0,\n\t\t},\n\t\t{\n\t\t\ttags:          model.KeyValues{},\n\t\t\texpectedType:  model.SamplerTypeUnrecognized,\n\t\t\texpectedParam: 0,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"lowerbound\"),\n\t\t\t\tmodel.String(\"sampler.param\", \"1\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeLowerBound,\n\t\t\texpectedParam: 1,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"lowerbound\"),\n\t\t\t\tmodel.Int64(\"sampler.param\", 1),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeLowerBound,\n\t\t\texpectedParam: 1,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"lowerbound\"),\n\t\t\t\tmodel.Float64(\"sampler.param\", 0.5),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeLowerBound,\n\t\t\texpectedParam: 0.5,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"lowerbound\"),\n\t\t\t\tmodel.String(\"sampler.param\", \"not_a_number\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeUnrecognized,\n\t\t\texpectedParam: 0,\n\t\t},\n\t\t{\n\t\t\ttags: model.KeyValues{\n\t\t\t\tmodel.String(\"sampler.type\", \"not_a_type\"),\n\t\t\t\tmodel.String(\"sampler.param\", \"not_a_number\"),\n\t\t\t},\n\t\t\texpectedType:  model.SamplerTypeUnrecognized,\n\t\t\texpectedParam: 0,\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\ttt := test\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tspan := &model.Span{}\n\t\t\tspan.Tags = tt.tags\n\t\t\tactualType, actualParam := getSamplerParams(span, logger)\n\t\t\tassert.Equal(t, tt.expectedType, actualType)\n\t\t\tassert.InDelta(t, tt.expectedParam, actualParam, 0.01)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/cache.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\n// SamplingCacheEntry keeps track of the probability and whether a service-operation is observed\n// using adaptive sampling.\ntype SamplingCacheEntry struct {\n\tProbability   float64\n\tUsingAdaptive bool\n}\n\n// SamplingCache is a nested map: service -> operation -> cache entry.\ntype SamplingCache map[string]map[string]*SamplingCacheEntry\n\n// Set adds a new entry for given service/operation.\nfunc (s SamplingCache) Set(service, operation string, entry *SamplingCacheEntry) {\n\tif _, ok := s[service]; !ok {\n\t\ts[service] = make(map[string]*SamplingCacheEntry)\n\t}\n\ts[service][operation] = entry\n}\n\n// Get retrieves the entry for given service/operation. Returns nil if not found.\nfunc (s SamplingCache) Get(service, operation string) *SamplingCacheEntry {\n\tv, ok := s[service]\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn v[operation]\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/cache_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSamplingCache(t *testing.T) {\n\tvar (\n\t\tc         = SamplingCache{}\n\t\tservice   = \"svc\"\n\t\toperation = \"op\"\n\t)\n\tc.Set(service, operation, &SamplingCacheEntry{})\n\tassert.NotNil(t, c.Get(service, operation))\n\tassert.Nil(t, c.Get(\"blah\", \"blah\"))\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/calculationstrategy/interface.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage calculationstrategy\n\n// ProbabilityCalculator calculates the new probability given the current and target QPS\ntype ProbabilityCalculator interface {\n\tCalculate(targetQPS, curQPS, prevProbability float64) (newProbability float64)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/calculationstrategy/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage calculationstrategy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/calculationstrategy/percentage_increase_capped_calculator.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage calculationstrategy\n\nconst (\n\tdefaultPercentageIncreaseCap = 0.5\n)\n\n// PercentageIncreaseCappedCalculator is a probability calculator that caps the probability\n// increase to a certain percentage of the previous probability.\n//\n// Given prevProb = 0.1, newProb = 0.5, and cap = 0.5:\n// (0.5 - 0.1)/0.1 = 400% increase. Given that our cap is 50%, the probability can only\n// increase to 0.15.\n//\n// Given prevProb = 0.4, newProb = 0.5, and cap = 0.5:\n// (0.5 - 0.4)/0.4 = 25% increase. Given that this is below our cap of 50%, the probability\n// can increase to 0.5.\ntype PercentageIncreaseCappedCalculator struct {\n\tpercentageIncreaseCap float64\n}\n\n// NewPercentageIncreaseCappedCalculator returns a new percentage increase capped calculator.\nfunc NewPercentageIncreaseCappedCalculator(percentageIncreaseCap float64) PercentageIncreaseCappedCalculator {\n\tif percentageIncreaseCap == 0 {\n\t\tpercentageIncreaseCap = defaultPercentageIncreaseCap\n\t}\n\treturn PercentageIncreaseCappedCalculator{\n\t\tpercentageIncreaseCap: percentageIncreaseCap,\n\t}\n}\n\n// Calculate calculates the new probability.\nfunc (c PercentageIncreaseCappedCalculator) Calculate(targetQPS, curQPS, prevProbability float64) float64 {\n\tfactor := targetQPS / curQPS\n\tnewProbability := prevProbability * factor\n\t// If curQPS is lower than the targetQPS, we need to increase the probability slowly to\n\t// defend against oversampling.\n\t// Else if curQPS is higher than the targetQPS, jump directly to the newProbability to\n\t// defend against oversampling.\n\tif factor > 1.0 {\n\t\tpercentIncrease := (newProbability - prevProbability) / prevProbability\n\t\tif percentIncrease > c.percentageIncreaseCap {\n\t\t\tnewProbability = prevProbability + (prevProbability * c.percentageIncreaseCap)\n\t\t}\n\t}\n\treturn newProbability\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/calculationstrategy/percentage_increase_capped_calculator_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage calculationstrategy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPercentageIncreaseCappedCalculator(t *testing.T) {\n\tcalculator := NewPercentageIncreaseCappedCalculator(0)\n\ttests := []struct {\n\t\ttargetQPS           float64\n\t\tcurQPS              float64\n\t\toldProbability      float64\n\t\texpectedProbability float64\n\t\ttestName            string\n\t}{\n\t\t{1.0, 2.0, 0.1, 0.05, \"test1\"},\n\t\t{1.0, 0.5, 0.1, 0.15, \"test2\"},\n\t\t{1.0, 0.8, 0.1, 0.125, \"test3\"},\n\t}\n\tfor _, tt := range tests {\n\t\tprobability := calculator.Calculate(tt.targetQPS, tt.curQPS, tt.oldProbability)\n\t\tassert.InDelta(t, tt.expectedProbability, probability, 1e-4, tt.testName)\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/floatutils.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"math\"\n\t\"strconv\"\n)\n\n// TruncateFloat truncates float to 6 decimal positions and converts to string.\nfunc TruncateFloat(v float64) string {\n\treturn strconv.FormatFloat(v, 'f', 6, 64)\n}\n\n// FloatEquals compares two floats with 10 decimal positions precision.\nfunc FloatEquals(a, b float64) bool {\n\treturn math.Abs(a-b) < 1e-10\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/floatutils_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTruncateFloat(t *testing.T) {\n\ttests := []struct {\n\t\tprob     float64\n\t\texpected string\n\t}{\n\t\t{prob: 1, expected: \"1.000000\"},\n\t\t{prob: 0.00001, expected: \"0.000010\"},\n\t\t{prob: 0.00230234, expected: \"0.002302\"},\n\t\t{prob: 0.1040445000, expected: \"0.104044\"},\n\t\t{prob: 0.10404450002098709, expected: \"0.104045\"},\n\t}\n\tfor _, test := range tests {\n\t\tassert.Equal(t, test.expected, TruncateFloat(test.prob))\n\t}\n}\n\nfunc TestFloatEquals(t *testing.T) {\n\ttests := []struct {\n\t\tf1    float64\n\t\tf2    float64\n\t\tequal bool\n\t}{\n\t\t{f1: 0.123456789123, f2: 0.123456789123, equal: true},\n\t\t{f1: 0.123456789123, f2: 0.123456789111, equal: true},\n\t\t{f1: 0.123456780000, f2: 0.123456781111, equal: false},\n\t}\n\tfor _, test := range tests {\n\t\tassert.Equal(t, test.equal, FloatEquals(test.f1, test.f2))\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/options.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"time\"\n)\n\n// Options holds configuration for the adaptive sampling strategy store.\n// The abbreviation SPS refers to \"samples-per-second\", which is the target\n// of the optimization/control implemented by the adaptive sampling.\ntype Options struct {\n\t// TargetSamplesPerSecond is the global target rate of samples per operation.\n\t// TODO implement manual overrides per service/operation.\n\tTargetSamplesPerSecond float64 `mapstructure:\"target_samples_per_second\"`\n\n\t// DeltaTolerance is the acceptable amount of deviation between the observed and the desired (target)\n\t// throughput for an operation, expressed as a ratio. For example, the value of 0.3 (30% deviation)\n\t// means that if abs((actual-expected) / expected) < 0.3, then the actual sampling rate is \"close enough\"\n\t// and the system does not need to send an updated sampling probability (the \"control signal\" u(t)\n\t// in the PID Controller terminology) to the sampler in the application.\n\t//\n\t// Increase this to reduce the amount of fluctuation in the calculated probabilities.\n\tDeltaTolerance float64 `mapstructure:\"delta_tolerance\"`\n\n\t// CalculationInterval determines how often new probabilities are calculated. E.g. if it is 1 minute,\n\t// new sampling probabilities are calculated once a minute and each bucket will contain 1 minute worth\n\t// of aggregated throughput data.\n\tCalculationInterval time.Duration `mapstructure:\"calculation_interval\"`\n\n\t// AggregationBuckets is the total number of aggregated throughput buckets kept in memory, ie. if\n\t// the CalculationInterval is 1 minute (each bucket contains 1 minute of thoughput data) and the\n\t// AggregationBuckets is 3, the adaptive sampling processor will keep at most 3 buckets in memory for\n\t// all operations.\n\t// TODO(wjang): Expand on why this is needed when BucketsForCalculation seems to suffice.\n\tAggregationBuckets int `mapstructure:\"aggregation_buckets\"`\n\n\t// BucketsForCalculation determines how many previous buckets used in calculating the weighted QPS,\n\t// ie. if BucketsForCalculation is 1, only the most recent bucket will be used in calculating the weighted QPS.\n\tBucketsForCalculation int `mapstructure:\"calculation_buckets\"`\n\n\t// Delay is the amount of time to delay probability generation by, ie. if the CalculationInterval\n\t// is 1 minute, the number of buckets is 10, and the delay is 2 minutes, then at one time\n\t// we'll have [now()-12m,now()-2m] range of throughput data in memory to base the calculations\n\t// off of. This delay is necessary to counteract the rate at which the jaeger clients poll for\n\t// the latest sampling probabilities. The default client poll rate is 1 minute, which means that\n\t// during any 1 minute interval, the clients will be fetching new probabilities in a uniformly\n\t// distributed manner throughout the 1 minute window. By setting the delay to 2 minutes, we can\n\t// guarantee that all clients can use the latest calculated probabilities for at least 1 minute.\n\tDelay time.Duration `mapstructure:\"calculation_delay\"`\n\n\t// InitialSamplingProbability is the initial sampling probability for all new operations.\n\tInitialSamplingProbability float64 `mapstructure:\"initial_sampling_probability\"`\n\n\t// MinSamplingProbability is the minimum sampling probability for all operations. ie. the calculated sampling\n\t// probability will be in the range [MinSamplingProbability, 1.0].\n\tMinSamplingProbability float64 `mapstructure:\"min_sampling_probability\"`\n\n\t// MinSamplesPerSecond determines the min number of traces that are sampled per second.\n\t// For example, if the value is 0.01666666666 (one every minute), then the sampling processor will do\n\t// its best to sample at least one trace a minute for an operation. This is useful for low QPS operations\n\t// that may never be sampled by the probabilistic sampler.\n\tMinSamplesPerSecond float64 `mapstructure:\"min_samples_per_second\"`\n\n\t// LeaderLeaseRefreshInterval is the duration to sleep if this processor is elected leader before\n\t// attempting to renew the lease on the leader lock. NB. This should be less than FollowerLeaseRefreshInterval\n\t// to reduce lock thrashing.\n\tLeaderLeaseRefreshInterval time.Duration `mapstructure:\"leader_lease_refresh_interval\"`\n\n\t// FollowerLeaseRefreshInterval is the duration to sleep if this processor is a follower\n\t// (ie. failed to gain the leader lock).\n\tFollowerLeaseRefreshInterval time.Duration `mapstructure:\"follower_lease_refresh_interval\"`\n}\n\nfunc DefaultOptions() Options {\n\treturn Options{\n\t\tTargetSamplesPerSecond:       1,\n\t\tDeltaTolerance:               0.3,\n\t\tBucketsForCalculation:        1,\n\t\tCalculationInterval:          time.Minute,\n\t\tAggregationBuckets:           10,\n\t\tDelay:                        time.Minute * 2,\n\t\tInitialSamplingProbability:   0.001,\n\t\tMinSamplingProbability:       1e-5,                                   // one in 100k requests\n\t\tMinSamplesPerSecond:          1.0 / float64(time.Minute/time.Second), // once every 1 minute\n\t\tLeaderLeaseRefreshInterval:   5 * time.Second,\n\t\tFollowerLeaseRefreshInterval: 60 * time.Second,\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/options_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDefaultOptions(t *testing.T) {\n\topts := DefaultOptions()\n\tassert.NotNil(t, opts)\n\tassert.InDelta(t, 1.0, opts.TargetSamplesPerSecond, 0.01)\n\tassert.InDelta(t, 0.3, opts.DeltaTolerance, 0.01)\n\tassert.Equal(t, 1, opts.BucketsForCalculation)\n\tassert.Equal(t, time.Minute, opts.CalculationInterval)\n\tassert.Equal(t, 10, opts.AggregationBuckets)\n\tassert.Equal(t, time.Minute*2, opts.Delay)\n\tassert.InDelta(t, 0.001, opts.InitialSamplingProbability, 0.0001)\n\tassert.InDelta(t, 1e-5, opts.MinSamplingProbability, 1e-6)\n\tassert.InDelta(t, 1.0/float64(time.Minute/time.Second), opts.MinSamplesPerSecond, 0.0001)\n\tassert.Equal(t, 5*time.Second, opts.LeaderLeaseRefreshInterval)\n\tassert.Equal(t, 60*time.Second, opts.FollowerLeaseRefreshInterval)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/post_aggregator.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"errors\"\n\t\"math\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/leaderelection\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/sampling/samplingstrategy/adaptive/calculationstrategy\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nconst (\n\tmaxSamplingProbability = 1.0\n\n\tgetThroughputErrMsg = \"failed to get throughput from storage\"\n\n\t// The number of past entries for samplingCache the leader keeps in memory\n\tserviceCacheSize = 25\n)\n\nvar (\n\terrNonZero               = errors.New(\"CalculationInterval and AggregationBuckets must be greater than 0\")\n\terrBucketsForCalculation = errors.New(\"BucketsForCalculation cannot be less than 1\")\n)\n\n// nested map: service -> operation -> throughput.\ntype serviceOperationThroughput map[string]map[string]*model.Throughput\n\nfunc (t serviceOperationThroughput) get(service, operation string) (*model.Throughput, bool) {\n\tsvcThroughput, ok := t[service]\n\tif ok {\n\t\tv, ok := svcThroughput[operation]\n\t\treturn v, ok\n\t}\n\treturn nil, false\n}\n\n// nested map: service -> operation -> buckets of QPS values.\ntype serviceOperationQPS map[string]map[string][]float64\n\ntype throughputBucket struct {\n\tthroughput serviceOperationThroughput\n\tinterval   time.Duration\n\tendTime    time.Time\n}\n\n// PostAggregator retrieves service throughput over a lookback interval and calculates sampling probabilities\n// per operation such that each operation is sampled at a specified target QPS. It achieves this by\n// retrieving discrete buckets of operation throughput and doing a weighted average of the throughput\n// and generating a probability to match the targetQPS.\ntype PostAggregator struct {\n\tOptions\n\n\tmu                  sync.RWMutex\n\telectionParticipant leaderelection.ElectionParticipant\n\tstorage             samplingstore.Store\n\tlogger              *zap.Logger\n\thostname            string\n\n\t// probabilities contains the latest calculated sampling probabilities for service operations.\n\tprobabilities model.ServiceOperationProbabilities\n\n\t// qps contains the latest calculated qps for service operations; the calculation is essentially\n\t// throughput / CalculationInterval.\n\tqps model.ServiceOperationQPS\n\n\t// throughputs is an  array (of `AggregationBuckets` size) that stores the aggregated throughput.\n\t// The latest throughput is stored at the head of the slice.\n\tthroughputs []*throughputBucket\n\n\tweightVectorCache *WeightVectorCache\n\n\tprobabilityCalculator calculationstrategy.ProbabilityCalculator\n\n\tserviceCache []SamplingCache\n\n\tshutdown chan struct{}\n\n\toperationsCalculatedGauge     metrics.Gauge\n\tcalculateProbabilitiesLatency metrics.Timer\n\tlastCheckedTime               time.Time\n}\n\n// newPostAggregator creates a new sampling postAggregator that generates sampling rates for service operations.\nfunc newPostAggregator(\n\topts Options,\n\thostname string,\n\tstorage samplingstore.Store,\n\telectionParticipant leaderelection.ElectionParticipant,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n) (*PostAggregator, error) {\n\tif opts.CalculationInterval == 0 || opts.AggregationBuckets == 0 {\n\t\treturn nil, errNonZero\n\t}\n\tif opts.BucketsForCalculation < 1 {\n\t\treturn nil, errBucketsForCalculation\n\t}\n\tmetricsFactory = metricsFactory.Namespace(metrics.NSOptions{Name: \"adaptive_sampling_processor\"})\n\treturn &PostAggregator{\n\t\tOptions:             opts,\n\t\tstorage:             storage,\n\t\tprobabilities:       make(model.ServiceOperationProbabilities),\n\t\tqps:                 make(model.ServiceOperationQPS),\n\t\thostname:            hostname,\n\t\tlogger:              logger,\n\t\telectionParticipant: electionParticipant,\n\t\t// TODO make weightsCache and probabilityCalculator configurable\n\t\tweightVectorCache:             NewWeightVectorCache(),\n\t\tprobabilityCalculator:         calculationstrategy.NewPercentageIncreaseCappedCalculator(1.0),\n\t\tserviceCache:                  []SamplingCache{},\n\t\toperationsCalculatedGauge:     metricsFactory.Gauge(metrics.Options{Name: \"operations_calculated\"}),\n\t\tcalculateProbabilitiesLatency: metricsFactory.Timer(metrics.TimerOptions{Name: \"calculate_probabilities\"}),\n\t\tshutdown:                      make(chan struct{}),\n\t}, nil\n}\n\n// Start initializes and starts the sampling postAggregator which regularly calculates sampling probabilities.\nfunc (p *PostAggregator) Start() {\n\tp.logger.Info(\"starting adaptive sampling postAggregator\")\n\t// NB: the first tick will be slightly delayed by the initializeThroughput call.\n\tp.lastCheckedTime = time.Now().Add(p.Delay * -1)\n\tp.initializeThroughput(p.lastCheckedTime)\n}\n\nfunc (p *PostAggregator) isLeader() bool {\n\treturn p.electionParticipant.IsLeader()\n}\n\n// addJitter adds a random amount of time. Without jitter, if the host holding the leader\n// lock were to die, then all other collectors can potentially wait for a full cycle before\n// trying to acquire the lock. With jitter, we can reduce the average amount of time before a\n// new leader is elected. Furthermore, jitter can be used to spread out read load on storage.\nfunc addJitter(jitterAmount time.Duration) time.Duration {\n\thalf := jitterAmount / 2\n\tif half <= 0 {\n\t\treturn jitterAmount\n\t}\n\treturn half + time.Duration(rand.Int63n(int64(half)))\n}\n\nfunc (p *PostAggregator) runCalculation() {\n\tendTime := time.Now().Add(p.Delay * -1)\n\tstartTime := p.lastCheckedTime\n\tthroughput, err := p.storage.GetThroughput(startTime, endTime)\n\tif err != nil {\n\t\tp.logger.Error(getThroughputErrMsg, zap.Error(err))\n\t\treturn\n\t}\n\taggregatedThroughput := p.aggregateThroughput(throughput)\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: aggregatedThroughput,\n\t\tinterval:   endTime.Sub(startTime),\n\t\tendTime:    endTime,\n\t})\n\tp.lastCheckedTime = endTime\n\t// Load the latest throughput so that if this host ever becomes leader, it\n\t// has the throughput ready in memory. However, only run the actual calculations\n\t// if this host becomes leader.\n\t// TODO fill the throughput buffer only when we're leader\n\tif p.isLeader() {\n\t\tstartTime := time.Now()\n\t\tprobabilities, qps := p.calculateProbabilitiesAndQPS()\n\t\tp.mu.Lock()\n\t\tp.probabilities = probabilities\n\t\tp.qps = qps\n\t\tp.mu.Unlock()\n\t\t// NB: This has the potential of running into a race condition if the CalculationInterval\n\t\t// is set to an extremely low value. The worst case scenario is that probabilities is calculated\n\t\t// and swapped more than once before generateStrategyResponses() and saveProbabilities() are called.\n\t\t// This will result in one or more batches of probabilities not being saved which is completely\n\t\t// fine. This race condition should not ever occur anyway since the calculation interval will\n\t\t// be way longer than the time to run the calculations.\n\n\t\tp.calculateProbabilitiesLatency.Record(time.Since(startTime))\n\t\tp.saveProbabilitiesAndQPS()\n\t}\n}\n\nfunc (p *PostAggregator) saveProbabilitiesAndQPS() {\n\tp.mu.RLock()\n\tdefer p.mu.RUnlock()\n\tif err := p.storage.InsertProbabilitiesAndQPS(p.hostname, p.probabilities, p.qps); err != nil {\n\t\tp.logger.Warn(\"could not save probabilities\", zap.Error(err))\n\t}\n}\n\nfunc (p *PostAggregator) prependThroughputBucket(bucket *throughputBucket) {\n\tp.throughputs = append([]*throughputBucket{bucket}, p.throughputs...)\n\tif len(p.throughputs) > p.AggregationBuckets {\n\t\tp.throughputs = p.throughputs[0:p.AggregationBuckets]\n\t}\n}\n\n// aggregateThroughput aggregates operation throughput from different buckets into one.\n// All input buckets represent a single time range, but there are many of them because\n// they are all independently generated by different collector instances from inbound span traffic.\nfunc (*PostAggregator) aggregateThroughput(throughputs []*model.Throughput) serviceOperationThroughput {\n\taggregatedThroughput := make(serviceOperationThroughput)\n\tfor _, throughput := range throughputs {\n\t\tservice := throughput.Service\n\t\toperation := throughput.Operation\n\t\tif _, ok := aggregatedThroughput[service]; !ok {\n\t\t\taggregatedThroughput[service] = make(map[string]*model.Throughput)\n\t\t}\n\t\tif t, ok := aggregatedThroughput[service][operation]; ok {\n\t\t\tt.Count += throughput.Count\n\t\t\tt.Probabilities = merge(t.Probabilities, throughput.Probabilities)\n\t\t} else {\n\t\t\tcopyThroughput := model.Throughput{\n\t\t\t\tService:       throughput.Service,\n\t\t\t\tOperation:     throughput.Operation,\n\t\t\t\tCount:         throughput.Count,\n\t\t\t\tProbabilities: copySet(throughput.Probabilities),\n\t\t\t}\n\t\t\taggregatedThroughput[service][operation] = &copyThroughput\n\t\t}\n\t}\n\treturn aggregatedThroughput\n}\n\nfunc copySet(in map[string]struct{}) map[string]struct{} {\n\tout := make(map[string]struct{}, len(in))\n\tfor key := range in {\n\t\tout[key] = struct{}{}\n\t}\n\treturn out\n}\n\nfunc (p *PostAggregator) initializeThroughput(endTime time.Time) {\n\tfor i := 0; i < p.AggregationBuckets; i++ {\n\t\tstartTime := endTime.Add(p.CalculationInterval * -1)\n\t\tthroughput, err := p.storage.GetThroughput(startTime, endTime)\n\t\tif err != nil && p.logger != nil {\n\t\t\tp.logger.Error(getThroughputErrMsg, zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\tif len(throughput) == 0 {\n\t\t\treturn\n\t\t}\n\t\taggregatedThroughput := p.aggregateThroughput(throughput)\n\t\tp.throughputs = append(p.throughputs, &throughputBucket{\n\t\t\tthroughput: aggregatedThroughput,\n\t\t\tinterval:   p.CalculationInterval,\n\t\t\tendTime:    endTime,\n\t\t})\n\t\tendTime = startTime\n\t}\n}\n\n// throughputToQPS converts raw throughput counts for all accumulated buckets to QPS values.\nfunc (p *PostAggregator) throughputToQPS() serviceOperationQPS {\n\t// TODO previous qps buckets have already been calculated, just need to calculate latest batch\n\t// and append them where necessary and throw out the oldest batch.\n\t// Edge case #buckets < p.AggregationBuckets, then we shouldn't throw out\n\tqps := make(serviceOperationQPS)\n\tfor _, bucket := range p.throughputs {\n\t\tfor svc, operations := range bucket.throughput {\n\t\t\tif _, ok := qps[svc]; !ok {\n\t\t\t\tqps[svc] = make(map[string][]float64)\n\t\t\t}\n\t\t\tfor op, throughput := range operations {\n\t\t\t\tif len(qps[svc][op]) >= p.BucketsForCalculation {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tqps[svc][op] = append(qps[svc][op], calculateQPS(throughput.Count, bucket.interval))\n\t\t\t}\n\t\t}\n\t}\n\treturn qps\n}\n\nfunc calculateQPS(count int64, interval time.Duration) float64 {\n\tseconds := float64(interval) / float64(time.Second)\n\treturn float64(count) / seconds\n}\n\n// calculateWeightedQPS calculates the weighted qps of the slice allQPS where weights are biased\n// towards more recent qps. This function assumes that the most recent qps is at the head of the slice.\nfunc (p *PostAggregator) calculateWeightedQPS(allQPS []float64) float64 {\n\tif len(allQPS) == 0 {\n\t\treturn 0\n\t}\n\tweights := p.weightVectorCache.GetWeights(len(allQPS))\n\tvar qps float64\n\tfor i := range allQPS {\n\t\t// #nosec G602 GetWeights always returns a slice of the same length as allQPS\n\t\tqps += allQPS[i] * weights[i]\n\t}\n\treturn qps\n}\n\nfunc (p *PostAggregator) prependServiceCache() {\n\tp.serviceCache = append([]SamplingCache{make(SamplingCache)}, p.serviceCache...)\n\tif len(p.serviceCache) > serviceCacheSize {\n\t\tp.serviceCache = p.serviceCache[0:serviceCacheSize]\n\t}\n}\n\nfunc (p *PostAggregator) calculateProbabilitiesAndQPS() (model.ServiceOperationProbabilities, model.ServiceOperationQPS) {\n\tp.prependServiceCache()\n\tretProbabilities := make(model.ServiceOperationProbabilities)\n\tretQPS := make(model.ServiceOperationQPS)\n\tsvcOpQPS := p.throughputToQPS()\n\ttotalOperations := int64(0)\n\tfor svc, opQPS := range svcOpQPS {\n\t\tif _, ok := retProbabilities[svc]; !ok {\n\t\t\tretProbabilities[svc] = make(map[string]float64)\n\t\t}\n\t\tif _, ok := retQPS[svc]; !ok {\n\t\t\tretQPS[svc] = make(map[string]float64)\n\t\t}\n\t\tfor op, qps := range opQPS {\n\t\t\ttotalOperations++\n\t\t\tavgQPS := p.calculateWeightedQPS(qps)\n\t\t\tretQPS[svc][op] = avgQPS\n\t\t\tretProbabilities[svc][op] = p.calculateProbability(svc, op, avgQPS)\n\t\t}\n\t}\n\tp.operationsCalculatedGauge.Update(totalOperations)\n\treturn retProbabilities, retQPS\n}\n\nfunc (p *PostAggregator) calculateProbability(service, operation string, qps float64) float64 {\n\toldProbability := p.InitialSamplingProbability\n\t// TODO: is this loop overly expensive?\n\tp.mu.RLock()\n\tif opProbabilities, ok := p.probabilities[service]; ok {\n\t\tif probability, ok := opProbabilities[operation]; ok {\n\t\t\toldProbability = probability\n\t\t}\n\t}\n\tlatestThroughput := p.throughputs[0].throughput\n\tp.mu.RUnlock()\n\n\tusingAdaptiveSampling := p.isUsingAdaptiveSampling(oldProbability, service, operation, latestThroughput)\n\tp.serviceCache[0].Set(service, operation, &SamplingCacheEntry{\n\t\tProbability:   oldProbability,\n\t\tUsingAdaptive: usingAdaptiveSampling,\n\t})\n\n\t// Short circuit if the qps is close enough to targetQPS or if the service doesn't appear to be using\n\t// adaptive sampling.\n\tif p.withinTolerance(qps, p.TargetSamplesPerSecond) || !usingAdaptiveSampling {\n\t\treturn oldProbability\n\t}\n\tvar newProbability float64\n\tif FloatEquals(qps, 0) {\n\t\t// Edge case; we double the sampling probability if the QPS is 0 so that we force the service\n\t\t// to at least sample one span probabilistically.\n\t\tnewProbability = oldProbability * 2.0\n\t} else {\n\t\tnewProbability = p.probabilityCalculator.Calculate(p.TargetSamplesPerSecond, qps, oldProbability)\n\t}\n\treturn math.Min(maxSamplingProbability, math.Max(p.MinSamplingProbability, newProbability))\n}\n\n// is actual value within p.DeltaTolerance percentage of expected value.\nfunc (p *PostAggregator) withinTolerance(actual, expected float64) bool {\n\treturn math.Abs(actual-expected)/expected < p.DeltaTolerance\n}\n\n// merge (union) string set p2 into string set p1\nfunc merge(p1 map[string]struct{}, p2 map[string]struct{}) map[string]struct{} {\n\tfor k := range p2 {\n\t\tp1[k] = struct{}{}\n\t}\n\treturn p1\n}\n\nfunc (p *PostAggregator) isUsingAdaptiveSampling(\n\tprobability float64,\n\tservice string,\n\toperation string,\n\tthroughput serviceOperationThroughput,\n) bool {\n\tif FloatEquals(probability, p.InitialSamplingProbability) {\n\t\t// If the service is seen for the first time, assume it's using adaptive sampling (ie prob == initialProb).\n\t\t// Even if this isn't the case, the next time around this loop, the newly calculated probability will not equal\n\t\t// the initialProb so the logic will fall through.\n\t\treturn true\n\t}\n\tif opThroughput, ok := throughput.get(service, operation); ok {\n\t\tf := TruncateFloat(probability)\n\t\t_, ok := opThroughput.Probabilities[f]\n\t\treturn ok\n\t}\n\t// By this point, we know that there's no recorded throughput for this operation for this round\n\t// of calculation. Check the previous bucket to see if this operation was using adaptive sampling\n\t// before.\n\tif len(p.serviceCache) > 1 {\n\t\tif e := p.serviceCache[1].Get(service, operation); e != nil {\n\t\t\treturn e.UsingAdaptive && !FloatEquals(e.Probability, p.InitialSamplingProbability)\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/post_aggregator_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\tepmocks \"github.com/jaegertracing/jaeger/internal/leaderelection/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/sampling/samplingstrategy/adaptive/calculationstrategy\"\n\tsmocks \"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc testThroughputs() []*model.Throughput {\n\treturn []*model.Throughput{\n\t\t{Service: \"svcA\", Operation: http.MethodGet, Count: 4, Probabilities: map[string]struct{}{\"0.1\": {}}},\n\t\t{Service: \"svcA\", Operation: http.MethodGet, Count: 4, Probabilities: map[string]struct{}{\"0.2\": {}}},\n\t\t{Service: \"svcA\", Operation: http.MethodPut, Count: 5, Probabilities: map[string]struct{}{\"0.1\": {}}},\n\t\t{Service: \"svcB\", Operation: http.MethodGet, Count: 3, Probabilities: map[string]struct{}{\"0.1\": {}}},\n\t}\n}\n\nfunc testThroughputBuckets() []*throughputBucket {\n\treturn []*throughputBucket{\n\t\t{\n\t\t\tthroughput: serviceOperationThroughput{\n\t\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 45},\n\t\t\t\t\thttp.MethodPut: {Count: 60},\n\t\t\t\t},\n\t\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 30},\n\t\t\t\t\thttp.MethodPut: {Count: 15},\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterval: 60 * time.Second,\n\t\t},\n\t\t{\n\t\t\tthroughput: serviceOperationThroughput{\n\t\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 30},\n\t\t\t\t},\n\t\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 45},\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterval: 60 * time.Second,\n\t\t},\n\t}\n}\n\nfunc errTestStorage() error {\n\treturn errors.New(\"storage error\")\n}\n\n// testProbabilityCalculator is a test implementation of ProbabilityCalculator\n// that calculates new probability by multiplying the old probability by the\n// ratio of target QPS to current QPS.\ntype testProbabilityCalculator struct{}\n\n// Calculate implements the ProbabilityCalculator interface for testing.\nfunc (testProbabilityCalculator) Calculate(targetQPS, qps, oldProbability float64) float64 {\n\tfactor := targetQPS / qps\n\treturn oldProbability * factor\n}\n\nfunc testCalculator() calculationstrategy.ProbabilityCalculator {\n\treturn testProbabilityCalculator{}\n}\n\nfunc TestAggregateThroughputInputsImmutability(t *testing.T) {\n\tp := &PostAggregator{}\n\tin := testThroughputs()\n\t_ = p.aggregateThroughput(in)\n\tassert.Equal(t, in, testThroughputs())\n}\n\nfunc TestAggregateThroughput(t *testing.T) {\n\tp := &PostAggregator{}\n\taggregatedThroughput := p.aggregateThroughput(testThroughputs())\n\trequire.Len(t, aggregatedThroughput, 2)\n\n\tthroughput, ok := aggregatedThroughput[\"svcA\"]\n\trequire.True(t, ok)\n\trequire.Len(t, throughput, 2)\n\n\topThroughput, ok := throughput[http.MethodGet]\n\trequire.True(t, ok)\n\tassert.Equal(t, int64(8), opThroughput.Count)\n\tassert.Equal(t, map[string]struct{}{\"0.1\": {}, \"0.2\": {}}, opThroughput.Probabilities)\n\n\topThroughput, ok = throughput[http.MethodPut]\n\trequire.True(t, ok)\n\tassert.Equal(t, int64(5), opThroughput.Count)\n\tassert.Equal(t, map[string]struct{}{\"0.1\": {}}, opThroughput.Probabilities)\n\n\tthroughput, ok = aggregatedThroughput[\"svcB\"]\n\trequire.True(t, ok)\n\trequire.Len(t, throughput, 1)\n\n\topThroughput, ok = throughput[http.MethodGet]\n\trequire.True(t, ok)\n\tassert.Equal(t, int64(3), opThroughput.Count)\n\tassert.Equal(t, map[string]struct{}{\"0.1\": {}}, opThroughput.Probabilities)\n}\n\nfunc TestInitializeThroughput(t *testing.T) {\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetThroughput\", time.Time{}.Add(time.Minute*19), time.Time{}.Add(time.Minute*20)).\n\t\tReturn(testThroughputs(), nil)\n\tmockStorage.On(\"GetThroughput\", time.Time{}.Add(time.Minute*18), time.Time{}.Add(time.Minute*19)).\n\t\tReturn([]*model.Throughput{{Service: \"svcA\", Operation: http.MethodGet, Count: 7}}, nil)\n\tmockStorage.On(\"GetThroughput\", time.Time{}.Add(time.Minute*17), time.Time{}.Add(time.Minute*18)).\n\t\tReturn([]*model.Throughput{}, nil)\n\tp := &PostAggregator{storage: mockStorage, Options: Options{CalculationInterval: time.Minute, AggregationBuckets: 3}}\n\tp.initializeThroughput(time.Time{}.Add(time.Minute * 20))\n\n\trequire.Len(t, p.throughputs, 2)\n\trequire.Len(t, p.throughputs[0].throughput, 2)\n\tassert.Equal(t, time.Minute, p.throughputs[0].interval)\n\tassert.Equal(t, p.throughputs[0].endTime, time.Time{}.Add(time.Minute*20))\n\trequire.Len(t, p.throughputs[1].throughput, 1)\n\tassert.Equal(t, time.Minute, p.throughputs[1].interval)\n\tassert.Equal(t, p.throughputs[1].endTime, time.Time{}.Add(time.Minute*19))\n}\n\nfunc TestInitializeThroughputFailure(t *testing.T) {\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetThroughput\", time.Time{}.Add(time.Minute*19), time.Time{}.Add(time.Minute*20)).\n\t\tReturn(nil, errTestStorage())\n\tp := &PostAggregator{storage: mockStorage, Options: Options{CalculationInterval: time.Minute, AggregationBuckets: 1}}\n\tp.initializeThroughput(time.Time{}.Add(time.Minute * 20))\n\n\tassert.Empty(t, p.throughputs)\n}\n\nfunc TestCalculateQPS(t *testing.T) {\n\tqps := calculateQPS(int64(90), 60*time.Second)\n\tassert.InDelta(t, 1.5, qps, 0.01)\n\n\tqps = calculateQPS(int64(45), 60*time.Second)\n\tassert.InDelta(t, 0.75, qps, 0.01)\n}\n\nfunc TestGenerateOperationQPS(t *testing.T) {\n\tp := &PostAggregator{throughputs: testThroughputBuckets(), Options: Options{BucketsForCalculation: 10, AggregationBuckets: 10}}\n\tsvcOpQPS := p.throughputToQPS()\n\tassert.Len(t, svcOpQPS, 2)\n\n\topQPS, ok := svcOpQPS[\"svcA\"]\n\trequire.True(t, ok)\n\trequire.Len(t, opQPS, 2)\n\n\tassert.Equal(t, []float64{0.75, 0.5}, opQPS[http.MethodGet])\n\tassert.Equal(t, []float64{1.0}, opQPS[http.MethodPut])\n\n\topQPS, ok = svcOpQPS[\"svcB\"]\n\trequire.True(t, ok)\n\trequire.Len(t, opQPS, 2)\n\n\tassert.Equal(t, []float64{0.5, 0.75}, opQPS[http.MethodGet])\n\tassert.Equal(t, []float64{0.25}, opQPS[http.MethodPut])\n\n\t// Test using the previous QPS if the throughput is not provided\n\tp.prependThroughputBucket(\n\t\t&throughputBucket{\n\t\t\tthroughput: serviceOperationThroughput{\n\t\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 30},\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterval: 60 * time.Second,\n\t\t},\n\t)\n\tsvcOpQPS = p.throughputToQPS()\n\trequire.Len(t, svcOpQPS, 2)\n\n\topQPS, ok = svcOpQPS[\"svcA\"]\n\trequire.True(t, ok)\n\trequire.Len(t, opQPS, 2)\n\n\tassert.Equal(t, []float64{0.5, 0.75, 0.5}, opQPS[http.MethodGet])\n\tassert.Equal(t, []float64{1.0}, opQPS[http.MethodPut])\n\n\topQPS, ok = svcOpQPS[\"svcB\"]\n\trequire.True(t, ok)\n\trequire.Len(t, opQPS, 2)\n\n\tassert.Equal(t, []float64{0.5, 0.75}, opQPS[http.MethodGet])\n\tassert.Equal(t, []float64{0.25}, opQPS[http.MethodPut])\n}\n\nfunc TestGenerateOperationQPS_UseMostRecentBucketOnly(t *testing.T) {\n\tp := &PostAggregator{throughputs: testThroughputBuckets(), Options: Options{BucketsForCalculation: 1, AggregationBuckets: 10}}\n\tsvcOpQPS := p.throughputToQPS()\n\tassert.Len(t, svcOpQPS, 2)\n\n\topQPS, ok := svcOpQPS[\"svcA\"]\n\trequire.True(t, ok)\n\trequire.Len(t, opQPS, 2)\n\n\tassert.Equal(t, []float64{0.75}, opQPS[http.MethodGet])\n\tassert.Equal(t, []float64{1.0}, opQPS[http.MethodPut])\n\n\tp.prependThroughputBucket(\n\t\t&throughputBucket{\n\t\t\tthroughput: serviceOperationThroughput{\n\t\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 30},\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterval: 60 * time.Second,\n\t\t},\n\t)\n\n\tsvcOpQPS = p.throughputToQPS()\n\trequire.Len(t, svcOpQPS, 2)\n\n\topQPS, ok = svcOpQPS[\"svcA\"]\n\trequire.True(t, ok)\n\trequire.Len(t, opQPS, 2)\n\n\tassert.Equal(t, []float64{0.5}, opQPS[http.MethodGet])\n\tassert.Equal(t, []float64{1.0}, opQPS[http.MethodPut])\n}\n\nfunc TestCalculateWeightedQPS(t *testing.T) {\n\tp := PostAggregator{weightVectorCache: NewWeightVectorCache()}\n\tassert.InDelta(t, 0.86735, p.calculateWeightedQPS([]float64{0.8, 1.2, 1.0}), 0.001)\n\tassert.InDelta(t, 0.95197, p.calculateWeightedQPS([]float64{1.0, 1.0, 0.0, 0.0}), 0.001)\n\tassert.InDelta(t, 0.0, p.calculateWeightedQPS([]float64{}), 0.01)\n}\n\nfunc TestCalculateProbability(t *testing.T) {\n\tthroughputs := []*throughputBucket{\n\t\t{\n\t\t\tthroughput: serviceOperationThroughput{\n\t\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Probabilities: map[string]struct{}{\"0.500000\": {}}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tprobabilities := model.ServiceOperationProbabilities{\n\t\t\"svcA\": map[string]float64{\n\t\t\thttp.MethodGet: 0.5,\n\t\t},\n\t}\n\tcfg := Options{\n\t\tTargetSamplesPerSecond:     1.0,\n\t\tDeltaTolerance:             0.2,\n\t\tInitialSamplingProbability: 0.001,\n\t\tMinSamplingProbability:     0.00001,\n\t}\n\tp := &PostAggregator{\n\t\tOptions:               cfg,\n\t\tprobabilities:         probabilities,\n\t\tprobabilityCalculator: testCalculator(),\n\t\tthroughputs:           throughputs,\n\t\tserviceCache:          []SamplingCache{{\"svcA\": {}, \"svcB\": {}}},\n\t}\n\ttests := []struct {\n\t\tservice             string\n\t\toperation           string\n\t\tqps                 float64\n\t\texpectedProbability float64\n\t\terrMsg              string\n\t}{\n\t\t{\"svcA\", http.MethodGet, 2.0, 0.25, \"modify existing probability\"},\n\t\t{\"svcA\", http.MethodPut, 2.0, 0.0005, \"modify default probability\"},\n\t\t{\"svcB\", http.MethodGet, 0.9, 0.001, \"qps within equivalence threshold\"},\n\t\t{\"svcB\", http.MethodPut, 0.000001, 1.0, \"test max probability\"},\n\t\t{\"svcB\", http.MethodDelete, 1000000000, 0.00001, \"test min probability\"},\n\t\t{\"svcB\", http.MethodDelete, 0.0, 0.002, \"test 0 qps\"},\n\t}\n\tfor _, test := range tests {\n\t\tprobability := p.calculateProbability(test.service, test.operation, test.qps)\n\t\tassert.InDelta(t, test.expectedProbability, probability, 1e-6, test.errMsg)\n\t}\n}\n\nfunc TestCalculateProbabilitiesAndQPS(t *testing.T) {\n\tprevProbabilities := model.ServiceOperationProbabilities{\n\t\t\"svcB\": map[string]float64{\n\t\t\thttp.MethodGet: 0.16,\n\t\t\thttp.MethodPut: 0.03,\n\t\t},\n\t}\n\tqps := model.ServiceOperationQPS{\n\t\t\"svcB\": map[string]float64{\n\t\t\thttp.MethodGet: 0.625,\n\t\t},\n\t}\n\tmets := metricstest.NewFactory(0)\n\tp := &PostAggregator{\n\t\tOptions: Options{\n\t\t\tTargetSamplesPerSecond:     1.0,\n\t\t\tDeltaTolerance:             0.2,\n\t\t\tInitialSamplingProbability: 0.001,\n\t\t\tBucketsForCalculation:      10,\n\t\t},\n\t\tthroughputs: testThroughputBuckets(), probabilities: prevProbabilities, qps: qps,\n\t\tweightVectorCache: NewWeightVectorCache(), probabilityCalculator: testCalculator(),\n\t\toperationsCalculatedGauge: mets.Gauge(metrics.Options{Name: \"test\"}),\n\t}\n\tprobabilities, qps := p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.00136, http.MethodPut: 0.001}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.16, http.MethodPut: 0.03}, probabilities[\"svcB\"])\n\n\trequire.Len(t, qps, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.7352941176470588, http.MethodPut: 1}, qps[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.5147058823529411, http.MethodPut: 0.25}, qps[\"svcB\"])\n\n\t_, gauges := mets.Backend.Snapshot()\n\tassert.EqualValues(t, 4, gauges[\"test\"])\n}\n\nfunc TestRunCalculationLoop(t *testing.T) {\n\tlogger := zap.NewNop()\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetThroughput\", mock.AnythingOfType(\"time.Time\"), mock.AnythingOfType(\"time.Time\")).\n\t\tReturn(testThroughputs(), nil)\n\tmockStorage.On(\"GetLatestProbabilities\").Return(model.ServiceOperationProbabilities{}, errTestStorage())\n\tmockStorage.On(\"InsertProbabilitiesAndQPS\", mock.AnythingOfType(\"string\"), mock.AnythingOfType(\"model.ServiceOperationProbabilities\"),\n\t\tmock.AnythingOfType(\"model.ServiceOperationQPS\")).Return(errTestStorage())\n\tmockStorage.On(\"InsertThroughput\", mock.AnythingOfType(\"[]*model.Throughput\")).Return(errTestStorage())\n\tmockEP := &epmocks.ElectionParticipant{}\n\tmockEP.On(\"Start\").Return(nil)\n\tmockEP.On(\"Close\").Return(nil)\n\tmockEP.On(\"IsLeader\").Return(true)\n\n\tcfg := Options{\n\t\tTargetSamplesPerSecond:       1.0,\n\t\tDeltaTolerance:               0.1,\n\t\tInitialSamplingProbability:   0.001,\n\t\tCalculationInterval:          time.Millisecond * 5,\n\t\tAggregationBuckets:           2,\n\t\tDelay:                        time.Millisecond * 5,\n\t\tLeaderLeaseRefreshInterval:   time.Millisecond,\n\t\tFollowerLeaseRefreshInterval: time.Second,\n\t\tBucketsForCalculation:        10,\n\t}\n\tagg, err := NewAggregator(cfg, logger, metrics.NullFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\tagg.Start()\n\tdefer agg.Close()\n\n\tfor range 1000 {\n\t\tagg.(*aggregator).Lock()\n\t\tprobabilities := agg.(*aggregator).postAggregator.probabilities\n\t\tagg.(*aggregator).Unlock()\n\t\tif len(probabilities) != 0 {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Millisecond)\n\t}\n\n\tpostAgg := agg.(*aggregator).postAggregator\n\tpostAgg.mu.Lock()\n\tprobabilities := postAgg.probabilities\n\tpostAgg.mu.Unlock()\n\trequire.Len(t, probabilities[\"svcA\"], 2)\n}\n\nfunc TestRunCalculationLoop_GetThroughputError(t *testing.T) {\n\tlogger, logBuffer := testutils.NewLogger()\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetThroughput\", mock.AnythingOfType(\"time.Time\"), mock.AnythingOfType(\"time.Time\")).\n\t\tReturn(nil, errTestStorage())\n\tmockStorage.On(\"GetLatestProbabilities\").Return(model.ServiceOperationProbabilities{}, errTestStorage())\n\tmockStorage.On(\"InsertProbabilitiesAndQPS\", mock.AnythingOfType(\"string\"), mock.AnythingOfType(\"model.ServiceOperationProbabilities\"),\n\t\tmock.AnythingOfType(\"model.ServiceOperationQPS\")).Return(errTestStorage())\n\tmockStorage.On(\"InsertThroughput\", mock.AnythingOfType(\"[]*model.Throughput\")).Return(errTestStorage())\n\n\tmockEP := &epmocks.ElectionParticipant{}\n\tmockEP.On(\"Start\").Return(nil)\n\tmockEP.On(\"Close\").Return(nil)\n\tmockEP.On(\"IsLeader\").Return(false)\n\n\tcfg := Options{\n\t\tCalculationInterval:   time.Millisecond * 5,\n\t\tAggregationBuckets:    2,\n\t\tBucketsForCalculation: 10,\n\t}\n\tagg, err := NewAggregator(cfg, logger, metrics.NullFactory, mockEP, mockStorage)\n\trequire.NoError(t, err)\n\tagg.Start()\n\tfor range 1000 {\n\t\t// match logs specific to getThroughputErrMsg. We expect to see more than 2, once during\n\t\t// initialization and one or more times during the loop.\n\t\tif match, _ := testutils.LogMatcher(2, getThroughputErrMsg, logBuffer.Lines()); match {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Millisecond)\n\t}\n\tmatch, errMsg := testutils.LogMatcher(2, getThroughputErrMsg, logBuffer.Lines())\n\tassert.True(t, match, errMsg)\n\trequire.NoError(t, agg.Close())\n}\n\nfunc TestPrependBucket(t *testing.T) {\n\tp := &PostAggregator{Options: Options{AggregationBuckets: 1}}\n\tp.prependThroughputBucket(&throughputBucket{interval: time.Minute})\n\trequire.Len(t, p.throughputs, 1)\n\tassert.Equal(t, time.Minute, p.throughputs[0].interval)\n\n\tp.prependThroughputBucket(&throughputBucket{interval: 2 * time.Minute})\n\trequire.Len(t, p.throughputs, 1)\n\tassert.Equal(t, 2*time.Minute, p.throughputs[0].interval)\n}\n\nfunc TestConstructorFailure(t *testing.T) {\n\tlogger := zap.NewNop()\n\n\tcfg := Options{\n\t\tTargetSamplesPerSecond:     1.0,\n\t\tDeltaTolerance:             0.2,\n\t\tInitialSamplingProbability: 0.001,\n\t\tCalculationInterval:        time.Second * 5,\n\t\tAggregationBuckets:         0,\n\t}\n\t_, err := newPostAggregator(cfg, \"host\", nil, nil, metrics.NullFactory, logger)\n\trequire.EqualError(t, err, \"CalculationInterval and AggregationBuckets must be greater than 0\")\n\n\tcfg.CalculationInterval = 0\n\t_, err = newPostAggregator(cfg, \"host\", nil, nil, metrics.NullFactory, logger)\n\trequire.EqualError(t, err, \"CalculationInterval and AggregationBuckets must be greater than 0\")\n\n\tcfg.CalculationInterval = time.Millisecond\n\tcfg.AggregationBuckets = 1\n\tcfg.BucketsForCalculation = -1\n\t_, err = newPostAggregator(cfg, \"host\", nil, nil, metrics.NullFactory, logger)\n\trequire.EqualError(t, err, \"BucketsForCalculation cannot be less than 1\")\n}\n\nfunc TestUsingAdaptiveSampling(t *testing.T) {\n\tp := &PostAggregator{}\n\tthroughput := serviceOperationThroughput{\n\t\t\"svc\": map[string]*model.Throughput{\n\t\t\t\"op\": {Probabilities: map[string]struct{}{\"0.010000\": {}}},\n\t\t},\n\t}\n\ttests := []struct {\n\t\texpected    bool\n\t\tprobability float64\n\t\tservice     string\n\t\toperation   string\n\t}{\n\t\t{expected: true, probability: 0.01, service: \"svc\", operation: \"op\"},\n\t\t{expected: true, probability: 0.0099999384, service: \"svc\", operation: \"op\"},\n\t\t{expected: false, probability: 0.01, service: \"non-svc\"},\n\t\t{expected: false, probability: 0.01, service: \"svc\", operation: \"non-op\"},\n\t\t{expected: false, probability: 0.01, service: \"svc\", operation: \"non-op\"},\n\t\t{expected: false, probability: 0.02, service: \"svc\", operation: \"op\"},\n\t\t{expected: false, probability: 0.0100009384, service: \"svc\", operation: \"op\"},\n\t}\n\tfor _, test := range tests {\n\t\tassert.Equal(t, test.expected, p.isUsingAdaptiveSampling(test.probability, test.service, test.operation, throughput))\n\t}\n}\n\nfunc TestPrependServiceCache(t *testing.T) {\n\tp := &PostAggregator{}\n\tfor range serviceCacheSize * 2 {\n\t\tp.prependServiceCache()\n\t}\n\tassert.Len(t, p.serviceCache, serviceCacheSize)\n}\n\nfunc TestCalculateProbabilitiesAndQPSMultiple(t *testing.T) {\n\tbuckets := []*throughputBucket{\n\t\t{\n\t\t\tthroughput: serviceOperationThroughput{\n\t\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodGet: {Count: 3, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\t\thttp.MethodPut: {Count: 60, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\t},\n\t\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\t\thttp.MethodPut: {Count: 15, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterval: 60 * time.Second,\n\t\t},\n\t}\n\n\tp := &PostAggregator{\n\t\tOptions: Options{\n\t\t\tTargetSamplesPerSecond:     1.0,\n\t\t\tDeltaTolerance:             0.002,\n\t\t\tInitialSamplingProbability: 0.001,\n\t\t\tBucketsForCalculation:      5,\n\t\t\tAggregationBuckets:         10,\n\t\t},\n\t\tthroughputs: buckets, probabilities: make(model.ServiceOperationProbabilities),\n\t\tqps: make(model.ServiceOperationQPS), weightVectorCache: NewWeightVectorCache(),\n\t\tprobabilityCalculator:     calculationstrategy.NewPercentageIncreaseCappedCalculator(1.0),\n\t\tserviceCache:              []SamplingCache{},\n\t\toperationsCalculatedGauge: metrics.NullFactory.Gauge(metrics.Options{}),\n\t}\n\n\tprobabilities, qps := p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.002, http.MethodPut: 0.001}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET is no longer reported, we should not increase it's probability since we don't know if it's adaptively sampled\n\t// until we get at least a lowerbound span or a probability span with the right probability.\n\t// svcB:PUT is only reporting lowerbound, we should boost it's probability\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 60, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\thttp.MethodPut: {Count: 0, Probabilities: map[string]struct{}{\"0.002000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.002, http.MethodPut: 0.001}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.004, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET is lower bound sampled, increase its probability\n\t// svcB:PUT is not reported but we should boost it's probability since the previous calculation showed that\n\t// it's using adaptive sampling\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 0, Probabilities: map[string]struct{}{\"0.002000\": {}}},\n\t\t\t\thttp.MethodPut: {Count: 60, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.004, http.MethodPut: 0.001}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.008, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET is finally adaptively probabilistically sampled!\n\t// svcB:PUT stopped using adaptive sampling\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 1, Probabilities: map[string]struct{}{\"0.004000\": {}}},\n\t\t\t\thttp.MethodPut: {Count: 60, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\thttp.MethodPut: {Count: 15, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.008, http.MethodPut: 0.001}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.008, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET didn't report anything\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\thttp.MethodPut: {Count: 15, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.016, http.MethodPut: 0.001468867216804201}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.008, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET didn't report anything\n\t// svcB:PUT starts to use adaptive sampling again\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\thttp.MethodPut: {Count: 1, Probabilities: map[string]struct{}{\"0.008000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.032, http.MethodPut: 0.001468867216804201}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.016, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET didn't report anything\n\t// svcB:PUT didn't report anything\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 30, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 15, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.064, http.MethodPut: 0.001468867216804201}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.032, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET didn't report anything\n\t// svcB:PUT didn't report anything\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 20, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodGet: {Count: 10, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.128, http.MethodPut: 0.001468867216804201}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.064, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n\n\t// svcA:GET didn't report anything\n\t// svcB:PUT didn't report anything\n\tp.prependThroughputBucket(&throughputBucket{\n\t\tthroughput: serviceOperationThroughput{\n\t\t\t\"svcA\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 20, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t\thttp.MethodGet: {Count: 120, Probabilities: map[string]struct{}{\"0.128000\": {}}},\n\t\t\t},\n\t\t\t\"svcB\": map[string]*model.Throughput{\n\t\t\t\thttp.MethodPut: {Count: 60, Probabilities: map[string]struct{}{\"0.064000\": {}}},\n\t\t\t\thttp.MethodGet: {Count: 10, Probabilities: map[string]struct{}{\"0.001000\": {}}},\n\t\t\t},\n\t\t},\n\t\tinterval: 60 * time.Second,\n\t})\n\n\tprobabilities, qps = p.calculateProbabilitiesAndQPS()\n\n\trequire.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.0882586677054928, http.MethodPut: 0.001468867216804201}, probabilities[\"svcA\"])\n\tassert.Equal(t, map[string]float64{http.MethodPut: 0.09587513707888091, http.MethodGet: 0.002}, probabilities[\"svcB\"])\n\n\tp.probabilities = probabilities\n\tp.qps = qps\n}\n\nfunc TestAddJitter(t *testing.T) {\n\t// zero duration: must not panic and must return 0\n\tassert.Equal(t, 0*time.Nanosecond, addJitter(0))\n\n\t// 1ns duration: jitterAmount/2 == 0, must not panic and must return 1ns\n\tassert.Equal(t, time.Nanosecond, addJitter(time.Nanosecond))\n\n\t// normal durations: result must be in [jitterAmount/2, jitterAmount)\n\tfor _, d := range []time.Duration{2 * time.Millisecond, time.Second, 20 * time.Second, 60 * time.Second} {\n\t\tfor range 100 {\n\t\t\tgot := addJitter(d)\n\t\t\tassert.GreaterOrEqual(t, got, d/2, \"addJitter(%v) below lower bound\", d)\n\t\t\tassert.Less(t, got, d, \"addJitter(%v) at or above upper bound\", d)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/provider.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/leaderelection\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nconst defaultFollowerProbabilityInterval = 20 * time.Second\n\n// Provider is responsible for providing sampling strategies for services.\n// It periodically loads sampling probabilities from storage and converts them\n// into sampling strategies that are cached and served to clients.\n// Provider relies on sampling probabilities being periodically updated by the\n// aggregator & post-aggregator.\ntype Provider struct {\n\tOptions\n\n\tmu                  sync.RWMutex\n\telectionParticipant leaderelection.ElectionParticipant\n\tstorage             samplingstore.Store\n\tlogger              *zap.Logger\n\n\t// probabilities contains the latest calculated sampling probabilities for service operations.\n\tprobabilities model.ServiceOperationProbabilities\n\n\t// strategyResponses is the cache of the sampling strategies for every service, in protobuf format.\n\tstrategyResponses map[string]*api_v2.SamplingStrategyResponse\n\n\t// followerRefreshInterval determines how often the follower processor updates its probabilities.\n\t// Given only the leader writes probabilities, the followers need to fetch the probabilities into\n\t// cache.\n\tfollowerRefreshInterval time.Duration\n\n\tshutdown   chan struct{}\n\tbgFinished sync.WaitGroup\n}\n\n// NewProvider creates a strategy store that holds adaptive sampling strategies.\nfunc NewProvider(options Options, logger *zap.Logger, participant leaderelection.ElectionParticipant, store samplingstore.Store) *Provider {\n\treturn &Provider{\n\t\tOptions:                 options,\n\t\tstorage:                 store,\n\t\tprobabilities:           make(model.ServiceOperationProbabilities),\n\t\tstrategyResponses:       make(map[string]*api_v2.SamplingStrategyResponse),\n\t\tlogger:                  logger,\n\t\telectionParticipant:     participant,\n\t\tfollowerRefreshInterval: defaultFollowerProbabilityInterval,\n\t\tshutdown:                make(chan struct{}),\n\t}\n}\n\n// Start initializes and starts the sampling service which regularly loads sampling probabilities and generates strategies.\nfunc (p *Provider) Start() error {\n\tp.logger.Info(\"starting adaptive sampling service\")\n\tp.loadProbabilities()\n\tp.generateStrategyResponses()\n\n\tp.bgFinished.Go(func() {\n\t\tp.runUpdateProbabilitiesLoop()\n\t})\n\n\treturn nil\n}\n\nfunc (p *Provider) loadProbabilities() {\n\t// TODO GetLatestProbabilities API can be changed to return the latest measured qps for initialization\n\tprobabilities, err := p.storage.GetLatestProbabilities()\n\tif err != nil {\n\t\tp.logger.Warn(\"failed to initialize probabilities\", zap.Error(err))\n\t\treturn\n\t}\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\tp.probabilities = probabilities\n}\n\n// runUpdateProbabilitiesLoop is a loop that reads probabilities from storage.\n// The follower updates its local cache with the latest probabilities and serves them.\nfunc (p *Provider) runUpdateProbabilitiesLoop() {\n\tselect {\n\tcase <-time.After(addJitter(p.followerRefreshInterval)):\n\t\t// continue after jitter delay\n\tcase <-p.shutdown:\n\t\treturn\n\t}\n\n\tticker := time.NewTicker(p.followerRefreshInterval)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\t// Only load probabilities if this strategy_store doesn't hold the leader lock\n\t\t\tif !p.isLeader() {\n\t\t\t\tp.loadProbabilities()\n\t\t\t\tp.generateStrategyResponses()\n\t\t\t}\n\t\tcase <-p.shutdown:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (p *Provider) isLeader() bool {\n\treturn p.electionParticipant.IsLeader()\n}\n\n// generateStrategyResponses generates and caches SamplingStrategyResponse from the calculated sampling probabilities.\nfunc (p *Provider) generateStrategyResponses() {\n\tp.mu.RLock()\n\tstrategies := make(map[string]*api_v2.SamplingStrategyResponse)\n\tfor svc, opProbabilities := range p.probabilities {\n\t\topStrategies := make([]*api_v2.OperationSamplingStrategy, len(opProbabilities))\n\t\tvar idx int\n\t\tfor op, probability := range opProbabilities {\n\t\t\topStrategies[idx] = &api_v2.OperationSamplingStrategy{\n\t\t\t\tOperation: op,\n\t\t\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\t\t\tSamplingRate: probability,\n\t\t\t\t},\n\t\t\t}\n\t\t\tidx++\n\t\t}\n\t\tstrategy := p.generateDefaultSamplingStrategyResponse()\n\t\tstrategy.OperationSampling.PerOperationStrategies = opStrategies\n\t\tstrategies[svc] = strategy\n\t}\n\tp.mu.RUnlock()\n\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\tp.strategyResponses = strategies\n}\n\nfunc (p *Provider) generateDefaultSamplingStrategyResponse() *api_v2.SamplingStrategyResponse {\n\treturn &api_v2.SamplingStrategyResponse{\n\t\tStrategyType: api_v2.SamplingStrategyType_PROBABILISTIC,\n\t\tOperationSampling: &api_v2.PerOperationSamplingStrategies{\n\t\t\tDefaultSamplingProbability:       p.InitialSamplingProbability,\n\t\t\tDefaultLowerBoundTracesPerSecond: p.MinSamplesPerSecond,\n\t\t},\n\t}\n}\n\n// GetSamplingStrategy implements protobuf endpoint for retrieving sampling strategy for a service.\nfunc (p *Provider) GetSamplingStrategy(_ context.Context, service string) (*api_v2.SamplingStrategyResponse, error) {\n\tp.mu.RLock()\n\tdefer p.mu.RUnlock()\n\tif strategy, ok := p.strategyResponses[service]; ok {\n\t\treturn strategy, nil\n\t}\n\treturn p.generateDefaultSamplingStrategyResponse(), nil\n}\n\n// Close stops the service from loading probabilities and generating strategies.\nfunc (p *Provider) Close() error {\n\tp.logger.Info(\"stopping adaptive sampling service\")\n\tclose(p.shutdown)\n\tp.bgFinished.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/provider_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\tepmocks \"github.com/jaegertracing/jaeger/internal/leaderelection/mocks\"\n\tsmocks \"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nfunc TestProviderLoadProbabilities(t *testing.T) {\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetLatestProbabilities\").Return(make(model.ServiceOperationProbabilities), nil)\n\n\tp := &Provider{storage: mockStorage}\n\trequire.Nil(t, p.probabilities)\n\tp.loadProbabilities()\n\trequire.NotNil(t, p.probabilities)\n}\n\nfunc TestProviderRunUpdateProbabilitiesLoop(t *testing.T) {\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetLatestProbabilities\").Return(make(model.ServiceOperationProbabilities), nil)\n\tmockEP := &epmocks.ElectionParticipant{}\n\tmockEP.On(\"Start\").Return(nil)\n\tmockEP.On(\"Close\").Return(nil)\n\tmockEP.On(\"IsLeader\").Return(false)\n\n\tp := &Provider{\n\t\tstorage:                 mockStorage,\n\t\tshutdown:                make(chan struct{}),\n\t\tfollowerRefreshInterval: time.Millisecond,\n\t\telectionParticipant:     mockEP,\n\t}\n\tdefer close(p.shutdown)\n\trequire.Nil(t, p.probabilities)\n\trequire.Nil(t, p.strategyResponses)\n\tgo p.runUpdateProbabilitiesLoop()\n\n\tfor range 1000 {\n\t\tp.mu.RLock()\n\t\tif p.probabilities != nil && p.strategyResponses != nil {\n\t\t\tp.mu.RUnlock()\n\t\t\tbreak\n\t\t}\n\t\tp.mu.RUnlock()\n\t\ttime.Sleep(time.Millisecond)\n\t}\n\tp.mu.RLock()\n\tassert.NotNil(t, p.probabilities)\n\tassert.NotNil(t, p.strategyResponses)\n\tp.mu.RUnlock()\n}\n\nfunc TestProviderRealisticRunCalculationLoop(t *testing.T) {\n\tt.Skip(\"Skipped realistic calculation loop test\")\n\tlogger := zap.NewNop()\n\t// NB: This is an extremely long test since it uses near realistic (1/6th scale) processor config values\n\ttestThroughputs := []*model.Throughput{\n\t\t{Service: \"svcA\", Operation: http.MethodGet, Count: 10},\n\t\t{Service: \"svcA\", Operation: http.MethodPost, Count: 9},\n\t\t{Service: \"svcA\", Operation: http.MethodPut, Count: 5},\n\t\t{Service: \"svcA\", Operation: http.MethodDelete, Count: 20},\n\t}\n\tmockStorage := &smocks.Store{}\n\tmockStorage.On(\"GetThroughput\", mock.AnythingOfType(\"time.Time\"), mock.AnythingOfType(\"time.Time\")).\n\t\tReturn(testThroughputs, nil)\n\tmockStorage.On(\"GetLatestProbabilities\").Return(make(model.ServiceOperationProbabilities), nil)\n\tmockStorage.On(\"InsertProbabilitiesAndQPS\", \"host\", mock.AnythingOfType(\"model.ServiceOperationProbabilities\"),\n\t\tmock.AnythingOfType(\"model.ServiceOperationQPS\")).Return(nil)\n\tmockEP := &epmocks.ElectionParticipant{}\n\tmockEP.On(\"Start\").Return(nil)\n\tmockEP.On(\"Close\").Return(nil)\n\tmockEP.On(\"IsLeader\").Return(true)\n\tcfg := Options{\n\t\tTargetSamplesPerSecond:     1.0,\n\t\tDeltaTolerance:             0.2,\n\t\tInitialSamplingProbability: 0.001,\n\t\tCalculationInterval:        time.Second * 10,\n\t\tAggregationBuckets:         1,\n\t\tDelay:                      time.Second * 10,\n\t}\n\ts := NewProvider(cfg, logger, mockEP, mockStorage)\n\ts.Start()\n\n\tfor range 100 {\n\t\tstrategy, _ := s.GetSamplingStrategy(context.Background(), \"svcA\")\n\t\tif len(strategy.OperationSampling.PerOperationStrategies) != 0 {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(250 * time.Millisecond)\n\t}\n\ts.Close()\n\n\tstrategy, err := s.GetSamplingStrategy(context.Background(), \"svcA\")\n\trequire.NoError(t, err)\n\trequire.Len(t, strategy.OperationSampling.PerOperationStrategies, 4)\n\tstrategies := strategy.OperationSampling.PerOperationStrategies\n\n\tfor _, s := range strategies {\n\t\tswitch s.Operation {\n\t\tcase http.MethodGet:\n\t\t\tassert.InDelta(t, 0.001, s.ProbabilisticSampling.SamplingRate, 1e-4,\n\t\t\t\t\"Already at 1QPS, no probability change\")\n\t\tcase http.MethodPost:\n\t\t\tassert.InDelta(t, 0.001, s.ProbabilisticSampling.SamplingRate, 1e-4,\n\t\t\t\t\"Within epsilon of 1QPS, no probability change\")\n\t\tcase http.MethodPut:\n\t\t\tassert.InEpsilon(t, 0.002, s.ProbabilisticSampling.SamplingRate, 0.025,\n\t\t\t\t\"Under sampled, double probability\")\n\t\tcase http.MethodDelete:\n\t\t\tassert.InEpsilon(t, 0.0005, s.ProbabilisticSampling.SamplingRate, 0.025,\n\t\t\t\t\"Over sampled, halve probability\")\n\t\tdefault:\n\t\t\tt.Errorf(\"Unexpected operation: %s\", s.Operation)\n\t\t}\n\t}\n}\n\nfunc TestProviderGenerateStrategyResponses(t *testing.T) {\n\tprobabilities := model.ServiceOperationProbabilities{\n\t\t\"svcA\": map[string]float64{\n\t\t\thttp.MethodGet: 0.5,\n\t\t},\n\t}\n\tp := &Provider{\n\t\tprobabilities: probabilities,\n\t\tOptions: Options{\n\t\t\tInitialSamplingProbability: 0.001,\n\t\t\tMinSamplesPerSecond:        0.0001,\n\t\t},\n\t}\n\tp.generateStrategyResponses()\n\n\texpectedResponse := map[string]*api_v2.SamplingStrategyResponse{\n\t\t\"svcA\": {\n\t\t\tStrategyType: api_v2.SamplingStrategyType_PROBABILISTIC,\n\t\t\tOperationSampling: &api_v2.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability:       0.001,\n\t\t\t\tDefaultLowerBoundTracesPerSecond: 0.0001,\n\t\t\t\tPerOperationStrategies: []*api_v2.OperationSamplingStrategy{\n\t\t\t\t\t{\n\t\t\t\t\t\tOperation: http.MethodGet,\n\t\t\t\t\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\t\t\t\t\tSamplingRate: 0.5,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tassert.Equal(t, expectedResponse, p.strategyResponses)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/weightvectorcache.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"math\"\n\t\"sync\"\n)\n\n// WeightVectorCache stores normalizing weight vectors of different lengths.\n// The head of each weight vector contains the largest weight.\ntype WeightVectorCache struct {\n\tmu    sync.Mutex\n\tcache map[int][]float64\n}\n\n// NewWeightVectorCache returns a new weights vector cache.\nfunc NewWeightVectorCache() *WeightVectorCache {\n\t// TODO allow users to plugin different weighting algorithms\n\treturn &WeightVectorCache{\n\t\tcache: make(map[int][]float64),\n\t}\n}\n\n// GetWeights returns weights for the specified length { w(i) = i ^ 4, i=1..L }, normalized.\nfunc (c *WeightVectorCache) GetWeights(length int) []float64 {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tif weights, ok := c.cache[length]; ok {\n\t\treturn weights\n\t}\n\tweights := make([]float64, 0, length)\n\tvar sum float64\n\tfor i := length; i > 0; i-- {\n\t\tw := math.Pow(float64(i), 4)\n\t\tweights = append(weights, w)\n\t\tsum += w\n\t}\n\t// normalize\n\tfor i := range length {\n\t\tweights[i] /= sum\n\t}\n\tc.cache[length] = weights\n\treturn weights\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/adaptive/weightvectorcache_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage adaptive\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetWeights(t *testing.T) {\n\tc := NewWeightVectorCache()\n\n\tweights := c.GetWeights(1)\n\tassert.Len(t, weights, 1)\n\n\tweights = c.GetWeights(3)\n\tassert.Len(t, weights, 3)\n\tassert.InDelta(t, 0.8265306122448979, weights[0], 0.001)\n\n\tweights = c.GetWeights(5)\n\tassert.Len(t, weights, 5)\n\tassert.InDelta(t, 0.6384, weights[0], 0.001)\n\tassert.InDelta(t, 0.0010, weights[4], 0.001)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/aggregator.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstrategy\n\nimport (\n\t\"io\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// Aggregator defines an interface used to aggregate operation throughput.\ntype Aggregator interface {\n\t// Close() from io.Closer stops the aggregator from aggregating throughput.\n\tio.Closer\n\n\t// The HandleRootSpan function processes a span, checking if it's a root span.\n\t// If it is, it extracts sampler parameters, then calls RecordThroughput.\n\tHandleRootSpan(span *model.Span)\n\n\t// RecordThroughput records throughput for an operation for aggregation.\n\tRecordThroughput(service, operation string, samplerType model.SamplerType, probability float64)\n\n\t// Start starts aggregating operation throughput.\n\tStart()\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/empty_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstrategy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/factory.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstrategy\n\nimport (\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n)\n\n// Factory defines an interface for a factory that can create implementations of different sampling strategy components.\n// Implementations are also encouraged to implement storage.Configurable interface.\n//\n// # See also\n//\n// storage.Configurable\ntype Factory interface {\n\t// Initialize performs internal initialization of the factory.\n\tInitialize(metricsFactory metrics.Factory, ssFactory storage.SamplingStoreFactory, logger *zap.Logger) error\n\n\t// CreateStrategyProvider initializes and returns Provider and optionallty Aggregator.\n\tCreateStrategyProvider() (Provider, Aggregator, error)\n\n\t// Close closes the factory\n\tClose() error\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/constants.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage file\n\nimport (\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n)\n\nconst (\n\t// samplerTypeProbabilistic is the type of sampler that samples traces\n\t// with a certain fixed probability.\n\tsamplerTypeProbabilistic = \"probabilistic\"\n\n\t// samplerTypeRateLimiting is the type of sampler that samples\n\t// only up to a fixed number of traces per second.\n\tsamplerTypeRateLimiting = \"ratelimiting\"\n\n\t// DefaultSamplingProbability is the default value for  \"DefaultSamplingProbability\"\n\t// used by the Strategy Store in case no DefaultSamplingProbability is defined\n\tDefaultSamplingProbability = 0.001\n)\n\n// defaultStrategy is the default sampling strategy the Strategy Store will return\n// if none is provided.\nfunc defaultStrategyResponse(defaultSamplingProbability float64) *api_v2.SamplingStrategyResponse {\n\treturn &api_v2.SamplingStrategyResponse{\n\t\tStrategyType: api_v2.SamplingStrategyType_PROBABILISTIC,\n\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\tSamplingRate: defaultSamplingProbability,\n\t\t},\n\t}\n}\n\nfunc defaultStrategies(defaultSamplingProbability float64) *storedStrategies {\n\ts := &storedStrategies{\n\t\tserviceStrategies: make(map[string]*api_v2.SamplingStrategyResponse),\n\t}\n\ts.defaultStrategy = defaultStrategyResponse(defaultSamplingProbability)\n\treturn s\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/TestServiceNoPerOperationStrategiesDeprecatedBehavior_ServiceA.json",
    "content": "{\n  \"probabilisticSampling\": {\n    \"samplingRate\": 1\n  },\n  \"operationSampling\": {\n    \"defaultSamplingProbability\": 1,\n    \"perOperationStrategies\": [\n      {\n        \"operation\": \"/health\",\n        \"probabilisticSampling\": {\n          \"samplingRate\": 0.1\n        }\n      }\n    ]\n  }\n}"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/TestServiceNoPerOperationStrategiesDeprecatedBehavior_ServiceB.json",
    "content": "{\n  \"strategyType\": 1,\n  \"rateLimitingSampling\": {\n    \"maxTracesPerSecond\": 3\n  }\n}"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/TestServiceNoPerOperationStrategies_ServiceA.json",
    "content": "{\n  \"probabilisticSampling\": {\n    \"samplingRate\": 1\n  },\n  \"operationSampling\": {\n    \"defaultSamplingProbability\": 1,\n    \"perOperationStrategies\": [\n      {\n        \"operation\": \"/health\",\n        \"probabilisticSampling\": {\n          \"samplingRate\": 0.1\n        }\n      }\n    ]\n  }\n}"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/TestServiceNoPerOperationStrategies_ServiceB.json",
    "content": "{\n  \"strategyType\": 1,\n  \"rateLimitingSampling\": {\n    \"maxTracesPerSecond\": 3\n  },\n  \"operationSampling\": {\n    \"defaultSamplingProbability\": 0.2,\n    \"perOperationStrategies\": [\n      {\n        \"operation\": \"/health\",\n        \"probabilisticSampling\": {\n          \"samplingRate\": 0.1\n        }\n      }\n    ]\n  }\n}"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/bad_strategies.json",
    "content": "\"nonsense\"\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/missing-service-types.json",
    "content": "{\n  \"default_strategy\": {\n    \"type\": \"probabilistic\",\n    \"param\": 0.5\n  },\n  \"service_strategies\": [\n    {\n      \"service\": \"foo\",\n      \"operation_strategies\": [\n        {\n          \"operation\": \"op1\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.2\n        }\n      ]\n    },\n    {\n      \"service\": \"bar\",\n      \"operation_strategies\": [\n        {\n          \"operation\": \"op3\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.3\n        },\n        {\n          \"operation\": \"op5\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.4\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/operation_strategies.json",
    "content": "{\n  \"default_strategy\": {\n    \"type\": \"probabilistic\",\n    \"param\": 0.5,\n    \"operation_strategies\": [\n      {\n        \"operation\": \"op0\",\n        \"type\": \"probabilistic\",\n        \"param\": 0.2\n      },\n      {\n        \"operation\": \"op6\",\n        \"type\": \"probabilistic\",\n        \"param\": 0\n      },\n      {\n        \"operation\": \"spam\",\n        \"type\": \"ratelimiting\",\n        \"param\": 1\n      },\n      {\n        \"operation\": \"op7\",\n        \"type\": \"probabilistic\",\n        \"param\": 1\n      }\n    ]\n  },\n  \"service_strategies\": [\n    {\n      \"service\": \"foo\",\n      \"type\": \"probabilistic\",\n      \"param\": 0.8,\n      \"operation_strategies\": [\n        {\n          \"operation\": \"op6\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.5\n        },\n        {\n          \"operation\": \"op1\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.2\n        },\n        {\n          \"operation\": \"op2\",\n          \"type\": \"ratelimiting\",\n          \"param\": 10\n        }\n      ]\n    },\n    {\n      \"service\": \"bar\",\n      \"type\": \"ratelimiting\",\n      \"param\": 5,\n      \"operation_strategies\": [\n        {\n          \"operation\": \"op3\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.3\n        },\n        {\n          \"operation\": \"op4\",\n          \"type\": \"ratelimiting\",\n          \"param\": 100\n        },\n        {\n          \"operation\": \"op5\",\n          \"type\": \"probabilistic\",\n          \"param\": 0.4\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/service_no_per_operation.json",
    "content": "{\n  \"service_strategies\": [\n    {\n      \"service\": \"ServiceA\",\n      \"type\": \"probabilistic\",\n      \"param\": 1.0\n    },\n    {\n      \"service\": \"ServiceB\",\n      \"type\": \"ratelimiting\",\n      \"param\": 3\n    }\n  ],\n  \"default_strategy\": {\n    \"type\": \"probabilistic\",\n    \"param\": 0.2,\n    \"operation_strategies\": [\n      {\n        \"operation\": \"/health\",\n        \"type\": \"probabilistic\",\n        \"param\": 0.1\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/fixtures/strategies.json",
    "content": "{\n  \"default_strategy\": {\n    \"type\": \"probabilistic\",\n    \"param\": 0.5\n  },\n  \"service_strategies\": [\n    {\n      \"service\": \"foo\",\n      \"type\": \"probabilistic\",\n      \"param\": 0.8\n    },\n    {\n      \"service\": \"bar\",\n      \"type\": \"ratelimiting\",\n      \"param\": 5\n    }\n  ]\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/options.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage file\n\nimport (\n\t\"time\"\n)\n\n// Options holds configuration for the static sampling strategy store.\ntype Options struct {\n\t// StrategiesFile is the path for the sampling strategies file in JSON format\n\tStrategiesFile string\n\t// ReloadInterval is the time interval to check and reload sampling strategies file\n\tReloadInterval time.Duration\n\t// DefaultSamplingProbability is the sampling probability used by the Strategy Store for static sampling\n\tDefaultSamplingProbability float64\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage file\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/provider.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage file\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/sampling/samplingstrategy\"\n)\n\n// null represents \"null\" JSON value and\n// it un-marshals to nil pointer.\nvar nullJSON = []byte(\"null\")\n\ntype samplingProvider struct {\n\tlogger *zap.Logger\n\n\tstoredStrategies atomic.Value // holds *storedStrategies\n\n\tcancelFunc context.CancelFunc\n\n\toptions Options\n}\n\ntype storedStrategies struct {\n\tdefaultStrategy   *api_v2.SamplingStrategyResponse\n\tserviceStrategies map[string]*api_v2.SamplingStrategyResponse\n}\n\ntype strategyLoader func() ([]byte, error)\n\n// NewProvider creates a strategy store that holds static sampling strategies.\nfunc NewProvider(options Options, logger *zap.Logger) (samplingstrategy.Provider, error) {\n\tctx, cancelFunc := context.WithCancel(context.Background())\n\th := &samplingProvider{\n\t\tlogger:     logger,\n\t\tcancelFunc: cancelFunc,\n\t\toptions:    options,\n\t}\n\th.storedStrategies.Store(defaultStrategies(options.DefaultSamplingProbability))\n\n\tif options.StrategiesFile == \"\" {\n\t\th.logger.Info(\"No sampling strategies source provided, using defaults\")\n\t\treturn h, nil\n\t}\n\n\tloadFn := h.samplingStrategyLoader(options.StrategiesFile)\n\tstrategies, err := loadStrategies(loadFn)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if strategies == nil {\n\t\th.logger.Info(\"No sampling strategies found or URL is unavailable, using defaults\")\n\t\treturn h, nil\n\t}\n\th.parseStrategies(strategies)\n\tif options.ReloadInterval > 0 {\n\t\tgo h.autoUpdateStrategies(ctx, loadFn)\n\t}\n\treturn h, nil\n}\n\n// GetSamplingStrategy implements StrategyStore#GetSamplingStrategy.\nfunc (h *samplingProvider) GetSamplingStrategy(_ context.Context, serviceName string) (*api_v2.SamplingStrategyResponse, error) {\n\tstoredStrategies := h.storedStrategies.Load().(*storedStrategies)\n\tserviceStrategies := storedStrategies.serviceStrategies\n\tif strategy, ok := serviceStrategies[serviceName]; ok {\n\t\treturn strategy, nil\n\t}\n\th.logger.Debug(\"sampling strategy not found, using default\", zap.String(\"service\", serviceName))\n\treturn storedStrategies.defaultStrategy, nil\n}\n\n// Close stops updating the strategies\nfunc (h *samplingProvider) Close() error {\n\th.cancelFunc()\n\treturn nil\n}\n\nfunc (h *samplingProvider) downloadSamplingStrategies(samplingURL string) ([]byte, error) {\n\th.logger.Info(\"Downloading sampling strategies\", zap.String(\"url\", samplingURL))\n\n\tctx, cx := context.WithTimeout(context.Background(), time.Second)\n\tdefer cx()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, samplingURL, http.NoBody)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot construct HTTP request: %w\", err)\n\t}\n\tresp, err := http.DefaultClient.Do(req) //nolint:gosec // G704 - URL from config\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to download sampling strategies: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbuf := new(bytes.Buffer)\n\tif _, err = buf.ReadFrom(resp.Body); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read sampling strategies HTTP response body: %w\", err)\n\t}\n\n\tif resp.StatusCode == http.StatusServiceUnavailable {\n\t\treturn nullJSON, nil\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"receiving %s while downloading strategies file: %s\",\n\t\t\tresp.Status,\n\t\t\tbuf.String(),\n\t\t)\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc isURL(str string) bool {\n\tu, err := url.Parse(str)\n\treturn err == nil && u.Scheme != \"\" && u.Host != \"\"\n}\n\nfunc (h *samplingProvider) samplingStrategyLoader(strategiesFile string) strategyLoader {\n\tif isURL(strategiesFile) {\n\t\treturn func() ([]byte, error) {\n\t\t\treturn h.downloadSamplingStrategies(strategiesFile)\n\t\t}\n\t}\n\n\treturn func() ([]byte, error) {\n\t\tcurrBytes, err := os.ReadFile(filepath.Clean(strategiesFile))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read strategies file %s: %w\", strategiesFile, err)\n\t\t}\n\t\treturn currBytes, nil\n\t}\n}\n\nfunc (h *samplingProvider) autoUpdateStrategies(ctx context.Context, loader strategyLoader) {\n\tlastValue := string(nullJSON)\n\tticker := time.NewTicker(h.options.ReloadInterval)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tlastValue = h.reloadSamplingStrategy(loader, lastValue)\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (h *samplingProvider) reloadSamplingStrategy(loadFn strategyLoader, lastValue string) string {\n\tnewValue, err := loadFn()\n\tif err != nil {\n\t\th.logger.Error(\"failed to re-load sampling strategies\", zap.Error(err))\n\t\treturn lastValue\n\t}\n\tif lastValue == string(newValue) {\n\t\treturn lastValue\n\t}\n\tif err := h.updateSamplingStrategy(newValue); err != nil {\n\t\th.logger.Error(\"failed to update sampling strategies\", zap.Error(err))\n\t\treturn lastValue\n\t}\n\treturn string(newValue)\n}\n\nfunc (h *samplingProvider) updateSamplingStrategy(dataBytes []byte) error {\n\tvar strategies strategies\n\tif err := json.Unmarshal(dataBytes, &strategies); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal sampling strategies: %w\", err)\n\t}\n\th.parseStrategies(&strategies)\n\th.logger.Info(\"Updated sampling strategies:\" + string(dataBytes))\n\treturn nil\n}\n\n// TODO good candidate for a global util function\nfunc loadStrategies(loadFn strategyLoader) (*strategies, error) {\n\tstrategyBytes, err := loadFn()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar strategies *strategies\n\tif err := json.Unmarshal(strategyBytes, &strategies); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal strategies: %w\", err)\n\t}\n\treturn strategies, nil\n}\n\nfunc (h *samplingProvider) parseStrategies(strategies *strategies) {\n\tnewStore := defaultStrategies(h.options.DefaultSamplingProbability)\n\tif strategies.DefaultStrategy != nil {\n\t\tnewStore.defaultStrategy = h.parseServiceStrategies(strategies.DefaultStrategy)\n\t}\n\n\tfor _, s := range strategies.ServiceStrategies {\n\t\tnewStore.serviceStrategies[s.Service] = h.parseServiceStrategies(s)\n\n\t\t// Config for this service may not have per-operation strategies,\n\t\t// but if the default strategy has them they should still apply.\n\n\t\tif newStore.defaultStrategy.OperationSampling == nil {\n\t\t\t// Default strategy doens't have them either, nothing to do.\n\t\t\tcontinue\n\t\t}\n\n\t\topS := newStore.serviceStrategies[s.Service].OperationSampling\n\t\tif opS == nil {\n\t\t\t// Service does not have its own per-operation rules, so copy (by value) from the default strategy.\n\t\t\tnewOpS := *newStore.defaultStrategy.OperationSampling\n\n\t\t\t// If the service's own default is probabilistic, then its sampling rate should take precedence.\n\t\t\tif newStore.serviceStrategies[s.Service].ProbabilisticSampling != nil {\n\t\t\t\tnewOpS.DefaultSamplingProbability = newStore.serviceStrategies[s.Service].ProbabilisticSampling.SamplingRate\n\t\t\t}\n\t\t\tnewStore.serviceStrategies[s.Service].OperationSampling = &newOpS\n\t\t\tcontinue\n\t\t}\n\n\t\t// If the service did have its own per-operation strategies, then merge them with the default ones.\n\t\topS.PerOperationStrategies = mergePerOperationSamplingStrategies(\n\t\t\topS.PerOperationStrategies,\n\t\t\tnewStore.defaultStrategy.OperationSampling.PerOperationStrategies)\n\t}\n\th.storedStrategies.Store(newStore)\n}\n\n// mergePerOperationSamplingStrategies merges two operation strategies a and b, where a takes precedence over b.\nfunc mergePerOperationSamplingStrategies(\n\ta, b []*api_v2.OperationSamplingStrategy,\n) []*api_v2.OperationSamplingStrategy {\n\tm := make(map[string]bool)\n\tfor _, aOp := range a {\n\t\tm[aOp.Operation] = true\n\t}\n\tfor _, bOp := range b {\n\t\tif m[bOp.Operation] {\n\t\t\tcontinue\n\t\t}\n\t\ta = append(a, bOp)\n\t}\n\treturn a\n}\n\nfunc (h *samplingProvider) parseServiceStrategies(strategy *serviceStrategy) *api_v2.SamplingStrategyResponse {\n\tresp := h.parseStrategy(&strategy.strategy)\n\tif len(strategy.OperationStrategies) == 0 {\n\t\treturn resp\n\t}\n\topS := &api_v2.PerOperationSamplingStrategies{\n\t\tDefaultSamplingProbability: h.options.DefaultSamplingProbability,\n\t}\n\tif resp.StrategyType == api_v2.SamplingStrategyType_PROBABILISTIC {\n\t\topS.DefaultSamplingProbability = resp.ProbabilisticSampling.SamplingRate\n\t}\n\tfor _, operationStrategy := range strategy.OperationStrategies {\n\t\ts, ok := h.parseOperationStrategy(operationStrategy, opS)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\topS.PerOperationStrategies = append(opS.PerOperationStrategies,\n\t\t\t&api_v2.OperationSamplingStrategy{\n\t\t\t\tOperation:             operationStrategy.Operation,\n\t\t\t\tProbabilisticSampling: s.ProbabilisticSampling,\n\t\t\t})\n\t}\n\tresp.OperationSampling = opS\n\treturn resp\n}\n\nfunc (h *samplingProvider) parseOperationStrategy(\n\tstrategy *operationStrategy,\n\tparent *api_v2.PerOperationSamplingStrategies,\n) (s *api_v2.SamplingStrategyResponse, ok bool) {\n\ts = h.parseStrategy(&strategy.strategy)\n\tif s.StrategyType == api_v2.SamplingStrategyType_RATE_LIMITING {\n\t\t// TODO OperationSamplingStrategy only supports probabilistic sampling\n\t\th.logger.Warn(\n\t\t\tfmt.Sprintf(\n\t\t\t\t\"Operation strategies only supports probabilistic sampling at the moment,\"+\n\t\t\t\t\t\"'%s' defaulting to probabilistic sampling with probability %f\",\n\t\t\t\tstrategy.Operation, parent.DefaultSamplingProbability),\n\t\t\tzap.Any(\"strategy\", strategy))\n\t\treturn nil, false\n\t}\n\treturn s, true\n}\n\nfunc (h *samplingProvider) parseStrategy(strategy *strategy) *api_v2.SamplingStrategyResponse {\n\tswitch strategy.Type {\n\tcase samplerTypeProbabilistic:\n\t\treturn &api_v2.SamplingStrategyResponse{\n\t\t\tStrategyType: api_v2.SamplingStrategyType_PROBABILISTIC,\n\t\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\t\tSamplingRate: strategy.Param,\n\t\t\t},\n\t\t}\n\tcase samplerTypeRateLimiting:\n\t\treturn &api_v2.SamplingStrategyResponse{\n\t\t\tStrategyType: api_v2.SamplingStrategyType_RATE_LIMITING,\n\t\t\tRateLimitingSampling: &api_v2.RateLimitingSamplingStrategy{\n\t\t\t\tMaxTracesPerSecond: int32(strategy.Param),\n\t\t\t},\n\t\t}\n\tdefault:\n\t\th.logger.Warn(\"Failed to parse sampling strategy\", zap.Any(\"strategy\", strategy))\n\t\treturn defaultStrategyResponse(h.options.DefaultSamplingProbability)\n\t}\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/provider_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage file\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest/observer\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst snapshotLocation = \"./fixtures/\"\n\n// Snapshots can be regenerated via:\n//\n//\tREGENERATE_SNAPSHOTS=true go test -v ./plugin/sampling/strategyprovider/static/provider_test.go\nvar regenerateSnapshots = os.Getenv(\"REGENERATE_SNAPSHOTS\") == \"true\"\n\n// strategiesJSON returns the strategy with\n// a given probability.\nfunc strategiesJSON(probability float32) string {\n\tstrategy := fmt.Sprintf(`\n\t\t{\n\t\t\t\"default_strategy\": {\n\t\t\t\t\"type\": \"probabilistic\",\n\t\t\t\t\"param\": 0.5\n\t\t\t},\n\t\t\t\"service_strategies\": [\n\t\t\t\t{\n\t\t\t\t\t\"service\": \"foo\",\n\t\t\t\t\t\"type\": \"probabilistic\",\n\t\t\t\t\t\"param\": %.1f\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"service\": \"bar\",\n\t\t\t\t\t\"type\": \"ratelimiting\",\n\t\t\t\t\t\"param\": 5\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t\t`,\n\t\tprobability,\n\t)\n\treturn strategy\n}\n\n// Returns strategies in JSON format. Used for testing\n// URL option for sampling strategies.\nfunc mockStrategyServer(t *testing.T) (*httptest.Server, *atomic.Pointer[string]) {\n\tvar strategy atomic.Pointer[string]\n\tvalue := strategiesJSON(0.8)\n\tstrategy.Store(&value)\n\tf := func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/bad-content\":\n\t\t\tw.Write([]byte(\"bad-content\"))\n\t\t\treturn\n\n\t\tcase \"/bad-status\":\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\treturn\n\n\t\tcase \"/service-unavailable\":\n\t\t\tw.WriteHeader(http.StatusServiceUnavailable)\n\t\t\treturn\n\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\tw.Write([]byte(*strategy.Load()))\n\t\t}\n\t}\n\tmockserver := httptest.NewServer(http.HandlerFunc(f))\n\tt.Cleanup(func() {\n\t\tmockserver.Close()\n\t})\n\treturn mockserver, &strategy\n}\n\nfunc TestStrategyStoreWithFile(t *testing.T) {\n\t_, err := NewProvider(Options{StrategiesFile: \"fileNotFound.json\", DefaultSamplingProbability: DefaultSamplingProbability}, zap.NewNop())\n\trequire.ErrorContains(t, err, \"failed to read strategies file fileNotFound.json\")\n\n\t_, err = NewProvider(Options{StrategiesFile: \"fixtures/bad_strategies.json\", DefaultSamplingProbability: DefaultSamplingProbability}, zap.NewNop())\n\trequire.EqualError(t, err,\n\t\t\"failed to unmarshal strategies: json: cannot unmarshal string into Go value of type file.strategies\")\n\n\t// Test default strategy\n\tlogger, buf := testutils.NewLogger()\n\tprovider, err := NewProvider(Options{DefaultSamplingProbability: DefaultSamplingProbability}, logger)\n\trequire.NoError(t, err)\n\tassert.Contains(t, buf.String(), \"No sampling strategies source provided, using defaults\")\n\ts, err := provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.001), *s)\n\n\t// Test reading strategies from a file\n\tprovider, err = NewProvider(Options{StrategiesFile: \"fixtures/strategies.json\"}, logger)\n\trequire.NoError(t, err)\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.8), *s)\n\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"bar\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_RATE_LIMITING, 5), *s)\n\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"default\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.5), *s)\n}\n\nfunc TestStrategyStoreWithURL(t *testing.T) {\n\t// Test default strategy when URL is temporarily unavailable.\n\tlogger, buf := testutils.NewLogger()\n\tmockServer, _ := mockStrategyServer(t)\n\tprovider, err := NewProvider(Options{StrategiesFile: mockServer.URL + \"/service-unavailable\", DefaultSamplingProbability: DefaultSamplingProbability}, logger)\n\trequire.NoError(t, err)\n\tassert.Contains(t, buf.String(), \"No sampling strategies found or URL is unavailable, using defaults\")\n\ts, err := provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.001), *s)\n\n\t// Test downloading strategies from a URL.\n\tprovider, err = NewProvider(Options{StrategiesFile: mockServer.URL}, logger)\n\trequire.NoError(t, err)\n\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.8), *s)\n\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"bar\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_RATE_LIMITING, 5), *s)\n}\n\nfunc TestPerOperationSamplingStrategies(t *testing.T) {\n\ttests := []struct {\n\t\toptions Options\n\t}{\n\t\t{Options{\n\t\t\tStrategiesFile:             \"fixtures/operation_strategies.json\",\n\t\t\tDefaultSamplingProbability: DefaultSamplingProbability,\n\t\t}},\n\t}\n\n\tfor _, tc := range tests {\n\t\tlogger, buf := testutils.NewLogger()\n\t\tprovider, err := NewProvider(tc.options, logger)\n\t\tassert.Contains(t, buf.String(), \"Operation strategies only supports probabilistic sampling at the moment,\"+\n\t\t\t\"'op2' defaulting to probabilistic sampling with probability 0.8\")\n\t\tassert.Contains(t, buf.String(), \"Operation strategies only supports probabilistic sampling at the moment,\"+\n\t\t\t\"'op4' defaulting to probabilistic sampling with probability 0.001\")\n\t\trequire.NoError(t, err)\n\n\t\texpected := makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.8)\n\n\t\ts, err := provider.GetSamplingStrategy(context.Background(), \"foo\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, api_v2.SamplingStrategyType_PROBABILISTIC, s.StrategyType)\n\t\tassert.Equal(t, *expected.ProbabilisticSampling, *s.ProbabilisticSampling)\n\n\t\trequire.NotNil(t, s.OperationSampling)\n\t\topSampling := s.OperationSampling\n\t\tassert.InDelta(t, 0.8, opSampling.DefaultSamplingProbability, 0.01)\n\t\trequire.Len(t, opSampling.PerOperationStrategies, 4)\n\n\t\tassert.Equal(t, \"op6\", opSampling.PerOperationStrategies[0].Operation)\n\t\tassert.InDelta(t, 0.5, opSampling.PerOperationStrategies[0].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op1\", opSampling.PerOperationStrategies[1].Operation)\n\t\tassert.InDelta(t, 0.2, opSampling.PerOperationStrategies[1].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op0\", opSampling.PerOperationStrategies[2].Operation)\n\t\tassert.InDelta(t, 0.2, opSampling.PerOperationStrategies[2].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op7\", opSampling.PerOperationStrategies[3].Operation)\n\t\tassert.InDelta(t, 1.0, opSampling.PerOperationStrategies[3].ProbabilisticSampling.SamplingRate, 0.01)\n\n\t\texpected = makeResponse(api_v2.SamplingStrategyType_RATE_LIMITING, 5)\n\n\t\ts, err = provider.GetSamplingStrategy(context.Background(), \"bar\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, api_v2.SamplingStrategyType_RATE_LIMITING, s.StrategyType)\n\t\tassert.Equal(t, *expected.RateLimitingSampling, *s.RateLimitingSampling)\n\n\t\trequire.NotNil(t, s.OperationSampling)\n\t\topSampling = s.OperationSampling\n\t\tassert.InDelta(t, 0.001, opSampling.DefaultSamplingProbability, 1e-4)\n\t\trequire.Len(t, opSampling.PerOperationStrategies, 5)\n\t\tassert.Equal(t, \"op3\", opSampling.PerOperationStrategies[0].Operation)\n\t\tassert.InDelta(t, 0.3, opSampling.PerOperationStrategies[0].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op5\", opSampling.PerOperationStrategies[1].Operation)\n\t\tassert.InDelta(t, 0.4, opSampling.PerOperationStrategies[1].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op0\", opSampling.PerOperationStrategies[2].Operation)\n\t\tassert.InDelta(t, 0.2, opSampling.PerOperationStrategies[2].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op6\", opSampling.PerOperationStrategies[3].Operation)\n\t\tassert.InDelta(t, 0.0, opSampling.PerOperationStrategies[3].ProbabilisticSampling.SamplingRate, 0.01)\n\t\tassert.Equal(t, \"op7\", opSampling.PerOperationStrategies[4].Operation)\n\t\tassert.InDelta(t, 1.0, opSampling.PerOperationStrategies[4].ProbabilisticSampling.SamplingRate, 0.01)\n\n\t\ts, err = provider.GetSamplingStrategy(context.Background(), \"default\")\n\t\trequire.NoError(t, err)\n\t\texpectedRsp := makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.5)\n\t\texpectedRsp.OperationSampling = &api_v2.PerOperationSamplingStrategies{\n\t\t\tDefaultSamplingProbability: 0.5,\n\t\t\tPerOperationStrategies: []*api_v2.OperationSamplingStrategy{\n\t\t\t\t{\n\t\t\t\t\tOperation: \"op0\",\n\t\t\t\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\t\t\t\tSamplingRate: 0.2,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tOperation: \"op6\",\n\t\t\t\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\t\t\t\tSamplingRate: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tOperation: \"op7\",\n\t\t\t\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\t\t\t\tSamplingRate: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tassert.Equal(t, expectedRsp, *s)\n\t}\n}\n\nfunc TestMissingServiceSamplingStrategyTypes(t *testing.T) {\n\tlogger, buf := testutils.NewLogger()\n\tprovider, err := NewProvider(Options{StrategiesFile: \"fixtures/missing-service-types.json\", DefaultSamplingProbability: DefaultSamplingProbability}, logger)\n\tassert.Contains(t, buf.String(), \"Failed to parse sampling strategy\")\n\trequire.NoError(t, err)\n\n\texpected := makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, DefaultSamplingProbability)\n\n\ts, err := provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, api_v2.SamplingStrategyType_PROBABILISTIC, s.StrategyType)\n\tassert.Equal(t, *expected.ProbabilisticSampling, *s.ProbabilisticSampling)\n\n\trequire.NotNil(t, s.OperationSampling)\n\topSampling := s.OperationSampling\n\tassert.InDelta(t, DefaultSamplingProbability, opSampling.DefaultSamplingProbability, 1e-4)\n\trequire.Len(t, opSampling.PerOperationStrategies, 1)\n\tassert.Equal(t, \"op1\", opSampling.PerOperationStrategies[0].Operation)\n\tassert.InDelta(t, 0.2, opSampling.PerOperationStrategies[0].ProbabilisticSampling.SamplingRate, 0.001)\n\n\texpected = makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, DefaultSamplingProbability)\n\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"bar\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, api_v2.SamplingStrategyType_PROBABILISTIC, s.StrategyType)\n\tassert.Equal(t, *expected.ProbabilisticSampling, *s.ProbabilisticSampling)\n\n\trequire.NotNil(t, s.OperationSampling)\n\topSampling = s.OperationSampling\n\tassert.InDelta(t, 0.001, opSampling.DefaultSamplingProbability, 1e-4)\n\trequire.Len(t, opSampling.PerOperationStrategies, 2)\n\tassert.Equal(t, \"op3\", opSampling.PerOperationStrategies[0].Operation)\n\tassert.InDelta(t, 0.3, opSampling.PerOperationStrategies[0].ProbabilisticSampling.SamplingRate, 0.01)\n\tassert.Equal(t, \"op5\", opSampling.PerOperationStrategies[1].Operation)\n\tassert.InDelta(t, 0.4, opSampling.PerOperationStrategies[1].ProbabilisticSampling.SamplingRate, 0.01)\n\n\ts, err = provider.GetSamplingStrategy(context.Background(), \"default\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.5), *s)\n}\n\nfunc TestParseStrategy(t *testing.T) {\n\ttests := []struct {\n\t\tstrategy serviceStrategy\n\t\texpected api_v2.SamplingStrategyResponse\n\t}{\n\t\t{\n\t\t\tstrategy: serviceStrategy{\n\t\t\t\tService:  \"svc\",\n\t\t\t\tstrategy: strategy{Type: \"probabilistic\", Param: 0.2},\n\t\t\t},\n\t\t\texpected: makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.2),\n\t\t},\n\t\t{\n\t\t\tstrategy: serviceStrategy{\n\t\t\t\tService:  \"svc\",\n\t\t\t\tstrategy: strategy{Type: \"ratelimiting\", Param: 3.5},\n\t\t\t},\n\t\t\texpected: makeResponse(api_v2.SamplingStrategyType_RATE_LIMITING, 3),\n\t\t},\n\t}\n\tlogger, buf := testutils.NewLogger()\n\tprovider := &samplingProvider{options: Options{DefaultSamplingProbability: DefaultSamplingProbability}, logger: logger}\n\tfor _, test := range tests {\n\t\ttt := test\n\t\tt.Run(\"\", func(t *testing.T) {\n\t\t\tassert.Equal(t, tt.expected, *provider.parseStrategy(&tt.strategy.strategy))\n\t\t})\n\t}\n\tassert.Empty(t, buf.String())\n\n\t// Test nonexistent strategy type\n\tactual := *provider.parseStrategy(&strategy{Type: \"blah\", Param: 3.5})\n\texpected := makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, provider.options.DefaultSamplingProbability)\n\tassert.Equal(t, expected, actual)\n\tassert.Contains(t, buf.String(), \"Failed to parse sampling strategy\")\n}\n\nfunc makeResponse(samplerType api_v2.SamplingStrategyType, param float64) (resp api_v2.SamplingStrategyResponse) {\n\tresp.StrategyType = samplerType\n\tswitch samplerType {\n\tcase api_v2.SamplingStrategyType_PROBABILISTIC:\n\t\tresp.ProbabilisticSampling = &api_v2.ProbabilisticSamplingStrategy{\n\t\t\tSamplingRate: param,\n\t\t}\n\tcase api_v2.SamplingStrategyType_RATE_LIMITING:\n\t\tresp.RateLimitingSampling = &api_v2.RateLimitingSamplingStrategy{\n\t\t\tMaxTracesPerSecond: int32(param),\n\t\t}\n\tdefault:\n\t}\n\treturn resp\n}\n\nfunc TestAutoUpdateStrategyWithFile(t *testing.T) {\n\ttempFile, _ := os.Create(t.TempDir() + \"for_go_test_*.json\")\n\trequire.NoError(t, tempFile.Close())\n\n\t// copy known fixture content into temp file which we can later overwrite\n\tsrcFile, dstFile := \"fixtures/strategies.json\", tempFile.Name()\n\tsrcBytes, err := os.ReadFile(srcFile)\n\trequire.NoError(t, err)\n\trequire.NoError(t, os.WriteFile(dstFile, srcBytes, 0o644))\n\n\tss, err := NewProvider(Options{\n\t\tStrategiesFile: dstFile,\n\t\tReloadInterval: time.Millisecond * 10,\n\t}, zap.NewNop())\n\trequire.NoError(t, err)\n\tprovider := ss.(*samplingProvider)\n\tdefer provider.Close()\n\n\t// confirm baseline value\n\ts, err := provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.8), *s)\n\n\t// verify that reloading is a no-op\n\tvalue := provider.reloadSamplingStrategy(provider.samplingStrategyLoader(dstFile), string(srcBytes))\n\tassert.Equal(t, string(srcBytes), value)\n\n\t// update file with new probability of 0.9\n\tnewStr := strings.Replace(string(srcBytes), \"0.8\", \"0.9\", 1)\n\trequire.NoError(t, os.WriteFile(dstFile, []byte(newStr), 0o644))\n\n\t// wait for reload timer\n\tfor range 1000 { // wait up to 1sec\n\t\ts, err = provider.GetSamplingStrategy(context.Background(), \"foo\")\n\t\trequire.NoError(t, err)\n\t\tif s.ProbabilisticSampling != nil && s.ProbabilisticSampling.SamplingRate == 0.9 {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(1 * time.Millisecond)\n\t}\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.9), *s)\n}\n\nfunc TestAutoUpdateStrategyWithURL(t *testing.T) {\n\tmockServer, mockStrategy := mockStrategyServer(t)\n\tss, err := NewProvider(Options{\n\t\tDefaultSamplingProbability: DefaultSamplingProbability,\n\t\tStrategiesFile:             mockServer.URL,\n\t\tReloadInterval:             10 * time.Millisecond,\n\t}, zap.NewNop())\n\trequire.NoError(t, err)\n\tprovider := ss.(*samplingProvider)\n\tdefer provider.Close()\n\n\t// confirm baseline value\n\ts, err := provider.GetSamplingStrategy(context.Background(), \"foo\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.8), *s)\n\n\t// verify that reloading in no-op\n\tvalue := provider.reloadSamplingStrategy(\n\t\tprovider.samplingStrategyLoader(mockServer.URL),\n\t\t*mockStrategy.Load(),\n\t)\n\tassert.Equal(t, *mockStrategy.Load(), value)\n\n\t// update original strategies with new probability of 0.9\n\t{\n\t\tv09 := strategiesJSON(0.9)\n\t\tmockStrategy.Store(&v09)\n\t}\n\n\t// wait for reload timer\n\tfor range 1000 { // wait up to 1sec\n\t\ts, err = provider.GetSamplingStrategy(context.Background(), \"foo\")\n\t\trequire.NoError(t, err)\n\t\tif s.ProbabilisticSampling != nil && s.ProbabilisticSampling.SamplingRate == 0.9 {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(1 * time.Millisecond)\n\t}\n\tassert.Equal(t, makeResponse(api_v2.SamplingStrategyType_PROBABILISTIC, 0.9), *s)\n}\n\nfunc TestAutoUpdateStrategyErrors(t *testing.T) {\n\ttempFile, _ := os.Create(t.TempDir() + \"for_go_test_*.json\")\n\trequire.NoError(t, tempFile.Close())\n\n\tzapCore, logs := observer.New(zap.InfoLevel)\n\tlogger := zap.New(zapCore)\n\n\ts, err := NewProvider(Options{\n\t\tStrategiesFile: \"fixtures/strategies.json\",\n\t\tReloadInterval: time.Hour,\n\t}, logger)\n\trequire.NoError(t, err)\n\tprovider := s.(*samplingProvider)\n\tdefer provider.Close()\n\n\t// check invalid file path or read failure\n\tassert.Equal(t, \"blah\", provider.reloadSamplingStrategy(provider.samplingStrategyLoader(tempFile.Name()+\"bad-path\"), \"blah\"))\n\tassert.Len(t, logs.FilterMessage(\"failed to re-load sampling strategies\").All(), 1)\n\n\t// check bad file content\n\trequire.NoError(t, os.WriteFile(tempFile.Name(), []byte(\"bad value\"), 0o644))\n\tassert.Equal(t, \"blah\", provider.reloadSamplingStrategy(provider.samplingStrategyLoader(tempFile.Name()), \"blah\"))\n\tassert.Len(t, logs.FilterMessage(\"failed to update sampling strategies\").All(), 1)\n\n\t// check invalid url\n\tassert.Equal(t, \"duh\", provider.reloadSamplingStrategy(provider.samplingStrategyLoader(\"bad-url\"), \"duh\"))\n\tassert.Len(t, logs.FilterMessage(\"failed to re-load sampling strategies\").All(), 2)\n\n\t// check status code other than 200\n\tmockServer, _ := mockStrategyServer(t)\n\tassert.Equal(t, \"duh\", provider.reloadSamplingStrategy(provider.samplingStrategyLoader(mockServer.URL+\"/bad-status\"), \"duh\"))\n\tassert.Len(t, logs.FilterMessage(\"failed to re-load sampling strategies\").All(), 3)\n\n\t// check bad content from url\n\tassert.Equal(t, \"duh\", provider.reloadSamplingStrategy(provider.samplingStrategyLoader(mockServer.URL+\"/bad-content\"), \"duh\"))\n\tassert.Len(t, logs.FilterMessage(\"failed to update sampling strategies\").All(), 2)\n}\n\nfunc TestServiceNoPerOperationStrategies(t *testing.T) {\n\t// given setup of strategy provider with no specific per operation sampling strategies\n\t// and option \"sampling.strategies.bugfix-5270=true\"\n\tprovider, err := NewProvider(Options{\n\t\tStrategiesFile: \"fixtures/service_no_per_operation.json\",\n\t}, zap.NewNop())\n\trequire.NoError(t, err)\n\n\tfor _, service := range []string{\"ServiceA\", \"ServiceB\"} {\n\t\tt.Run(service, func(t *testing.T) {\n\t\t\tstrategy, err := provider.GetSamplingStrategy(context.Background(), service)\n\t\t\trequire.NoError(t, err)\n\t\t\tstrategyJson, err := json.MarshalIndent(strategy, \"\", \"  \")\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttestName := strings.ReplaceAll(t.Name(), \"/\", \"_\")\n\t\t\tsnapshotFile := filepath.Join(snapshotLocation, testName+\".json\")\n\t\t\texpectedServiceResponse, err := os.ReadFile(snapshotFile)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.JSONEq(t, string(expectedServiceResponse), string(strategyJson),\n\t\t\t\t\"comparing against stored snapshot. Use REGENERATE_SNAPSHOTS=true to rebuild snapshots.\")\n\n\t\t\tif regenerateSnapshots {\n\t\t\t\tos.WriteFile(snapshotFile, strategyJson, 0o644)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSamplingStrategyLoader(t *testing.T) {\n\tprovider := &samplingProvider{logger: zap.NewNop()}\n\t// invalid file path\n\tloader := provider.samplingStrategyLoader(\"not-exists\")\n\t_, err := loader()\n\trequire.ErrorContains(t, err, \"failed to read strategies file not-exists\")\n\n\t// status code other than 200\n\tmockServer, _ := mockStrategyServer(t)\n\tloader = provider.samplingStrategyLoader(mockServer.URL + \"/bad-status\")\n\t_, err = loader()\n\trequire.ErrorContains(t, err, \"receiving 404 Not Found while downloading strategies file\")\n\n\t// should download content from URL\n\tloader = provider.samplingStrategyLoader(mockServer.URL + \"/bad-content\")\n\tcontent, err := loader()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"bad-content\", string(content))\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/file/strategy.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage file\n\n// strategy defines a sampling strategy. Type can be \"probabilistic\" or \"ratelimiting\"\n// and Param will represent \"sampling probability\" and \"max traces per second\" respectively.\ntype strategy struct {\n\tType  string  `json:\"type\"`\n\tParam float64 `json:\"param\"`\n}\n\n// operationStrategy defines an operation specific sampling strategy.\ntype operationStrategy struct {\n\tOperation string `json:\"operation\"`\n\tstrategy\n}\n\n// serviceStrategy defines a service specific sampling strategy.\ntype serviceStrategy struct {\n\tService             string               `json:\"service\"`\n\tOperationStrategies []*operationStrategy `json:\"operation_strategies\"`\n\tstrategy\n}\n\n// strategies holds a default sampling strategy and service specific sampling strategies.\ntype strategies struct {\n\tDefaultStrategy   *serviceStrategy   `json:\"default_strategy\"`\n\tServiceStrategies []*serviceStrategy `json:\"service_strategies\"`\n}\n"
  },
  {
    "path": "internal/sampling/samplingstrategy/provider.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstrategy\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n)\n\n// Provider keeps track of service specific sampling strategies.\ntype Provider interface {\n\t// Close() from io.Closer  stops the processor from calculating probabilities.\n\tio.Closer\n\n\t// GetSamplingStrategy retrieves the sampling strategy for the specified service.\n\tGetSamplingStrategy(ctx context.Context, serviceName string) (*api_v2.SamplingStrategyResponse, error)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/config/config.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\t\"github.com/apache/cassandra-gocql-driver/v2/snappy\"\n\t\"github.com/asaskevich/govalidator\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n)\n\n// Configuration describes the configuration properties needed to connect to a Cassandra cluster.\ntype Configuration struct {\n\tSchema     Schema     `mapstructure:\"schema\"`\n\tConnection Connection `mapstructure:\"connection\"`\n\tQuery      Query      `mapstructure:\"query\"`\n}\n\ntype Connection struct {\n\t// Servers contains a list of hosts that are used to connect to the cluster.\n\tServers []string `mapstructure:\"servers\" valid:\"required,url\"`\n\t// LocalDC contains the name of the local Data Center (DC) for DC-aware host selection\n\tLocalDC string `mapstructure:\"local_dc\"`\n\t// The port used when dialing to a cluster.\n\tPort int `mapstructure:\"port\"`\n\t// DisableAutoDiscovery, if set to true, will disable the cluster's auto-discovery features.\n\tDisableAutoDiscovery bool `mapstructure:\"disable_auto_discovery\"`\n\t// ConnectionsPerHost contains the maximum number of open connections for each host on the cluster.\n\tConnectionsPerHost int `mapstructure:\"connections_per_host\"`\n\t// ReconnectInterval contains the regular interval after which the driver tries to connect to\n\t// nodes that are down.\n\tReconnectInterval time.Duration `mapstructure:\"reconnect_interval\"`\n\t// SocketKeepAlive contains the keep alive period for the default dialer to the cluster.\n\tSocketKeepAlive time.Duration `mapstructure:\"socket_keep_alive\"`\n\t// TLS contains the TLS configuration for the connection to the cluster.\n\tTLS configtls.ClientConfig `mapstructure:\"tls\"`\n\t// Timeout contains the maximum time spent to connect to a cluster.\n\tTimeout time.Duration `mapstructure:\"timeout\"`\n\t// Authenticator contains the details of the authentication mechanism that is used for\n\t// connecting to a cluster.\n\tAuthenticator Authenticator `mapstructure:\"auth\"`\n\t// ProtoVersion contains the version of the native protocol to use when connecting to a cluster.\n\tProtoVersion int `mapstructure:\"proto_version\"`\n}\n\ntype Schema struct {\n\t// Keyspace contains the namespace where Jaeger data will be stored.\n\tKeyspace string `mapstructure:\"keyspace\"`\n\t// DisableCompression, if set to true, disables the use of the default Snappy Compression\n\t// while connecting to the Cassandra Cluster. This is useful for connecting to clusters, like Azure Cosmos DB,\n\t// that do not support SnappyCompression.\n\tDisableCompression bool `mapstructure:\"disable_compression\"`\n\t// CreateSchema tells if the schema ahould be created during session initialization based on the configs provided\n\tCreateSchema bool `mapstructure:\"create\" valid:\"optional\"`\n\t// Datacenter is the name for network topology\n\tDatacenter string `mapstructure:\"datacenter\" valid:\"optional\"`\n\t// TraceTTL is Time To Live (TTL) for the trace data. Should at least be 1 second\n\tTraceTTL time.Duration `mapstructure:\"trace_ttl\" valid:\"optional\"`\n\t// DependenciesTTL is Time To Live (TTL) for dependencies data. Should at least be 1 second\n\tDependenciesTTL time.Duration `mapstructure:\"dependencies_ttl\" valid:\"optional\"`\n\t// Replication factor for the db\n\tReplicationFactor int `mapstructure:\"replication_factor\" valid:\"optional\"`\n\t// CompactionWindow is the size of the window for TimeWindowCompactionStrategy.\n\t// All SSTables within that window are grouped together into one SSTable.\n\t// Ideally, operators should select a compaction window size that produces approximately less than 50 windows.\n\t// For example, if writing with a 90 day TTL, a 3 day window would be a reasonable choice.\n\tCompactionWindow time.Duration `mapstructure:\"compaction_window\" valid:\"optional\"`\n}\n\ntype Query struct {\n\t// Timeout contains the maximum time spent executing a query.\n\tTimeout time.Duration `mapstructure:\"timeout\"`\n\t// MaxRetryAttempts indicates the maximum number of times a query will be retried for execution.\n\tMaxRetryAttempts int `mapstructure:\"max_retry_attempts\"`\n\t// Consistency specifies the consistency level which needs to be satisified before responding\n\t// to a query.\n\tConsistency string `mapstructure:\"consistency\"`\n}\n\n// Authenticator holds the authentication properties needed to connect to a Cassandra cluster.\ntype Authenticator struct {\n\tBasic BasicAuthenticator `mapstructure:\"basic\"`\n\t// TODO: add more auth types\n}\n\n// BasicAuthenticator holds the username and password for a password authenticator for a Cassandra cluster.\ntype BasicAuthenticator struct {\n\tUsername              string   `mapstructure:\"username\"`\n\tPassword              string   `mapstructure:\"password\" json:\"-\"`\n\tAllowedAuthenticators []string `mapstructure:\"allowed_authenticators\"`\n}\n\nfunc DefaultConfiguration() Configuration {\n\treturn Configuration{\n\t\tSchema: Schema{\n\t\t\tCreateSchema:      false,\n\t\t\tKeyspace:          \"jaeger_dc1\",\n\t\t\tDatacenter:        \"dc1\",\n\t\t\tTraceTTL:          2 * 24 * time.Hour,\n\t\t\tDependenciesTTL:   2 * 24 * time.Hour,\n\t\t\tReplicationFactor: 1,\n\t\t\tCompactionWindow:  2 * time.Hour,\n\t\t},\n\t\tConnection: Connection{\n\t\t\tServers:            []string{\"127.0.0.1\"},\n\t\t\tPort:               9042,\n\t\t\tProtoVersion:       4,\n\t\t\tConnectionsPerHost: 2,\n\t\t\tReconnectInterval:  60 * time.Second,\n\t\t},\n\t\tQuery: Query{\n\t\t\tMaxRetryAttempts: 3,\n\t\t},\n\t}\n}\n\n// ApplyDefaults copies settings from source unless its own value is non-zero.\nfunc (c *Configuration) ApplyDefaults(source *Configuration) {\n\tif c.Schema.Keyspace == \"\" {\n\t\tc.Schema.Keyspace = source.Schema.Keyspace\n\t}\n\n\tif c.Schema.Datacenter == \"\" {\n\t\tc.Schema.Datacenter = source.Schema.Datacenter\n\t}\n\n\tif c.Schema.TraceTTL == 0 {\n\t\tc.Schema.TraceTTL = source.Schema.TraceTTL\n\t}\n\n\tif c.Schema.DependenciesTTL == 0 {\n\t\tc.Schema.DependenciesTTL = source.Schema.DependenciesTTL\n\t}\n\n\tif c.Schema.ReplicationFactor == 0 {\n\t\tc.Schema.ReplicationFactor = source.Schema.ReplicationFactor\n\t}\n\n\tif c.Schema.CompactionWindow == 0 {\n\t\tc.Schema.CompactionWindow = source.Schema.CompactionWindow\n\t}\n\n\tif c.Connection.ConnectionsPerHost == 0 {\n\t\tc.Connection.ConnectionsPerHost = source.Connection.ConnectionsPerHost\n\t}\n\tif c.Connection.ReconnectInterval == 0 {\n\t\tc.Connection.ReconnectInterval = source.Connection.ReconnectInterval\n\t}\n\tif c.Connection.Port == 0 {\n\t\tc.Connection.Port = source.Connection.Port\n\t}\n\tif c.Connection.ProtoVersion == 0 {\n\t\tc.Connection.ProtoVersion = source.Connection.ProtoVersion\n\t}\n\tif c.Connection.SocketKeepAlive == 0 {\n\t\tc.Connection.SocketKeepAlive = source.Connection.SocketKeepAlive\n\t}\n\tif c.Query.MaxRetryAttempts == 0 {\n\t\tc.Query.MaxRetryAttempts = source.Query.MaxRetryAttempts\n\t}\n\tif c.Query.Timeout == 0 {\n\t\tc.Query.Timeout = source.Query.Timeout\n\t}\n}\n\n// NewCluster creates a new gocql cluster from the configuration\nfunc (c *Configuration) NewCluster() (*gocql.ClusterConfig, error) {\n\tcluster := gocql.NewCluster(c.Connection.Servers...)\n\tcluster.Keyspace = c.Schema.Keyspace\n\tcluster.NumConns = c.Connection.ConnectionsPerHost\n\tcluster.ConnectTimeout = c.Connection.Timeout\n\tcluster.ReconnectInterval = c.Connection.ReconnectInterval\n\tcluster.SocketKeepalive = c.Connection.SocketKeepAlive\n\tcluster.Timeout = c.Query.Timeout\n\tif c.Connection.ProtoVersion > 0 {\n\t\tcluster.ProtoVersion = c.Connection.ProtoVersion\n\t}\n\tif c.Query.MaxRetryAttempts > 1 {\n\t\tcluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: c.Query.MaxRetryAttempts - 1}\n\t}\n\tif c.Connection.Port != 0 {\n\t\tcluster.Port = c.Connection.Port\n\t}\n\n\tif !c.Schema.DisableCompression {\n\t\tcluster.Compressor = &snappy.SnappyCompressor{}\n\t}\n\n\tif c.Query.Consistency == \"\" {\n\t\tcluster.Consistency = gocql.LocalOne\n\t} else {\n\t\tcluster.Consistency = gocql.ParseConsistency(c.Query.Consistency)\n\t}\n\n\tfallbackHostSelectionPolicy := gocql.RoundRobinHostPolicy()\n\tif c.Connection.LocalDC != \"\" {\n\t\tfallbackHostSelectionPolicy = gocql.DCAwareRoundRobinPolicy(c.Connection.LocalDC)\n\t}\n\tcluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(fallbackHostSelectionPolicy, gocql.ShuffleReplicas())\n\n\tif c.Connection.Authenticator.Basic.Username != \"\" && c.Connection.Authenticator.Basic.Password != \"\" {\n\t\tcluster.Authenticator = gocql.PasswordAuthenticator{\n\t\t\tUsername:              c.Connection.Authenticator.Basic.Username,\n\t\t\tPassword:              c.Connection.Authenticator.Basic.Password,\n\t\t\tAllowedAuthenticators: c.Connection.Authenticator.Basic.AllowedAuthenticators,\n\t\t}\n\t}\n\tif !c.Connection.TLS.Insecure {\n\t\ttlsCfg, err := c.Connection.TLS.LoadTLSConfig(context.Background())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcluster.SslOpts = &gocql.SslOptions{\n\t\t\tConfig: tlsCfg,\n\t\t}\n\t}\n\t// If tunneling connection to C*, disable cluster autodiscovery features.\n\tif c.Connection.DisableAutoDiscovery {\n\t\tcluster.DisableInitialHostLookup = true\n\t\tcluster.IgnorePeerAddr = true\n\t}\n\treturn cluster, nil\n}\n\nfunc (c *Configuration) String() string {\n\treturn fmt.Sprintf(\"%+v\", *c)\n}\n\nfunc isValidTTL(duration time.Duration) bool {\n\treturn duration == 0 || duration >= time.Second\n}\n\nfunc (c *Configuration) Validate() error {\n\t_, err := govalidator.ValidateStruct(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !isValidTTL(c.Schema.TraceTTL) {\n\t\treturn errors.New(\"trace_ttl can either be 0 or greater than or equal to 1 second\")\n\t}\n\n\tif !isValidTTL(c.Schema.DependenciesTTL) {\n\t\treturn errors.New(\"dependencies_ttl can either be 0 or greater than or equal to 1 second\")\n\t}\n\n\tif c.Schema.CompactionWindow < time.Minute {\n\t\treturn errors.New(\"compaction_window should at least be 1 minute\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/cassandra/config/config_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidate_ReturnsErrorWhenInvalid(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  *Configuration\n\t}{\n\t\t{\n\t\t\tname: \"missing required fields\",\n\t\t\tcfg:  &Configuration{},\n\t\t},\n\t\t{\n\t\t\tname: \"require fields in invalid format\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tConnection: Connection{\n\t\t\t\t\tServers: []string{\"not a url\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\terr := test.cfg.Validate()\n\t\t\trequire.Error(t, err)\n\t\t})\n\t}\n}\n\nfunc TestValidate_DoesNotReturnErrorWhenRequiredFieldsSet(t *testing.T) {\n\tcfg := Configuration{\n\t\tConnection: Connection{\n\t\t\tServers: []string{\"localhost:9200\"},\n\t\t},\n\t\tSchema: Schema{\n\t\t\tCompactionWindow: time.Minute,\n\t\t},\n\t}\n\n\terr := cfg.Validate()\n\trequire.NoError(t, err)\n}\n\nfunc TestNewClusterWithDefaults(t *testing.T) {\n\tcfg := DefaultConfiguration()\n\tcl, err := cfg.NewCluster()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, cl.Keyspace)\n}\n\nfunc TestNewClusterWithOverrides(t *testing.T) {\n\tcfg := DefaultConfiguration()\n\tcfg.Query.Consistency = \"LOCAL_QUORUM\"\n\tcfg.Connection.LocalDC = \"local_dc\"\n\tcfg.Connection.Authenticator.Basic.Username = \"username\"\n\tcfg.Connection.Authenticator.Basic.Password = \"password\"\n\tcfg.Connection.TLS.Insecure = false\n\tcfg.Connection.DisableAutoDiscovery = true\n\tcl, err := cfg.NewCluster()\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, cl.Keyspace)\n\tassert.Equal(t, gocql.LocalQuorum, cl.Consistency)\n\tassert.NotNil(t, cl.PoolConfig.HostSelectionPolicy, \"local_dc\")\n\trequire.IsType(t, gocql.PasswordAuthenticator{}, cl.Authenticator)\n\tauth := cl.Authenticator.(gocql.PasswordAuthenticator)\n\tassert.Equal(t, \"username\", auth.Username)\n\tassert.Equal(t, \"password\", auth.Password)\n\tassert.NotNil(t, cl.SslOpts)\n\tassert.True(t, cl.DisableInitialHostLookup)\n}\n\nfunc TestApplyDefaults(t *testing.T) {\n\tcfg1 := DefaultConfiguration()\n\tcfg2 := Configuration{}\n\tcfg2.ApplyDefaults(&cfg1)\n\tassert.Equal(t, cfg2.Schema, cfg1.Schema)\n\tassert.Equal(t, cfg2.Query, cfg1.Query)\n\tassert.NotEqual(t, cfg2.Connection.Servers, cfg1.Connection.Servers, \"servers not copied\")\n\tcfg1.Connection.Servers = nil\n\tassert.Equal(t, cfg2.Connection, cfg1.Connection)\n}\n\nfunc TestToString(t *testing.T) {\n\tcfg := DefaultConfiguration()\n\tcfg.Schema.Keyspace = \"test\"\n\ts := cfg.String()\n\tassert.Contains(t, s, \"Keyspace:test\")\n}\n\nfunc TestConfigSchemaValidation(t *testing.T) {\n\tcfg := DefaultConfiguration()\n\terr := cfg.Validate()\n\trequire.NoError(t, err)\n\n\tcfg.Schema.TraceTTL = time.Millisecond\n\terr = cfg.Validate()\n\trequire.Error(t, err)\n\n\tcfg.Schema.TraceTTL = time.Second\n\tcfg.Schema.CompactionWindow = time.Minute - 1\n\terr = cfg.Validate()\n\trequire.Error(t, err)\n\n\tcfg.Schema.CompactionWindow = time.Minute\n\tcfg.Schema.DependenciesTTL = time.Second - 1\n\terr = cfg.Validate()\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/config/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/empty_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/gocql/empty_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage gocql\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/gocql/gocql.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage gocql\n\nimport (\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n)\n\n// CQLSession is a wrapper around gocql.Session.\ntype CQLSession struct {\n\tsession *gocql.Session\n}\n\n// WrapCQLSession creates a Session out of *gocql.Session.\nfunc WrapCQLSession(session *gocql.Session) CQLSession {\n\treturn CQLSession{session: session}\n}\n\n// Query delegates to gocql.Session#Query and wraps the result as Query.\nfunc (s CQLSession) Query(stmt string, values ...any) cassandra.Query {\n\treturn WrapCQLQuery(s.session.Query(stmt, values...))\n}\n\n// Close delegates to gocql.Session#Close.\nfunc (s CQLSession) Close() {\n\ts.session.Close()\n}\n\n// ---\n\n// CQLQuery is a wrapper around gocql.Query.\ntype CQLQuery struct {\n\tquery *gocql.Query\n}\n\n// WrapCQLQuery creates a Query out of *gocql.Query.\nfunc WrapCQLQuery(query *gocql.Query) CQLQuery {\n\treturn CQLQuery{query: query}\n}\n\n// Exec delegates to gocql.Query#Exec.\nfunc (q CQLQuery) Exec() error {\n\treturn q.query.Exec()\n}\n\n// ScanCAS delegates to gocql.Query#ScanCAS.\nfunc (q CQLQuery) ScanCAS(dest ...any) (bool, error) {\n\treturn q.query.ScanCAS(dest...)\n}\n\n// Iter delegates to gocql.Query#Iter and wraps the result as Iterator.\nfunc (q CQLQuery) Iter() cassandra.Iterator {\n\treturn WrapCQLIterator(q.query.Iter())\n}\n\n// Bind delegates to gocql.Query#Bind and wraps the result as Query.\nfunc (q CQLQuery) Bind(v ...any) cassandra.Query {\n\treturn WrapCQLQuery(q.query.Bind(v...))\n}\n\n// Consistency delegates to gocql.Query#Consistency and wraps the result as Query.\nfunc (q CQLQuery) Consistency(level cassandra.Consistency) cassandra.Query {\n\treturn WrapCQLQuery(q.query.Consistency(gocql.Consistency(level)))\n}\n\n// String returns string representation of this query.\nfunc (q CQLQuery) String() string {\n\treturn q.query.String()\n}\n\n// PageSize delegates to gocql.Query#PageSize and wraps the result as Query.\nfunc (q CQLQuery) PageSize(n int) cassandra.Query {\n\treturn WrapCQLQuery(q.query.PageSize(n))\n}\n\n// ---\n\n// CQLIterator is a wrapper around gocql.Iter.\ntype CQLIterator struct {\n\titer *gocql.Iter\n}\n\n// WrapCQLIterator creates an Iterator out of *gocql.Iter.\nfunc WrapCQLIterator(iter *gocql.Iter) CQLIterator {\n\treturn CQLIterator{iter: iter}\n}\n\n// Scan delegates to gocql.Iter#Scan.\nfunc (i CQLIterator) Scan(dest ...any) bool {\n\treturn i.iter.Scan(dest...)\n}\n\n// Close delegates to gocql.Iter#Close.\nfunc (i CQLIterator) Close() error {\n\treturn i.iter.Close()\n}\n"
  },
  {
    "path": "internal/storage/cassandra/gocql/testutils/udt.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage testutils\n\nimport (\n\t\"testing\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// UDTField describes a field in a gocql User Defined Type\ntype UDTField struct {\n\tName  string\n\tType  gocql.Type\n\tValIn []byte // value to attempt to marshal\n\tErr   bool   // is error expected?\n}\n\n// UDTTestCase describes a test for a UDT\ntype UDTTestCase struct {\n\tObj     gocql.UDTMarshaler\n\tObjName string\n\tNew     func() gocql.UDTUnmarshaler\n\tFields  []UDTField\n}\n\n// Run runs a test case\nfunc (testCase UDTTestCase) Run(t *testing.T) {\n\tfor _, ff := range testCase.Fields {\n\t\tfield := ff // capture loop var\n\t\tt.Run(testCase.ObjName+\"-\"+field.Name, func(t *testing.T) {\n\t\t\t// Create TypeInfo using NewNativeType\n\t\t\t// For error test cases with invalid type, use TypeVarchar as a default\n\t\t\tfieldType := field.Type\n\t\t\tif fieldType == 0 {\n\t\t\t\tfieldType = gocql.TypeVarchar\n\t\t\t}\n\t\t\ttypeInfo := gocql.NewNativeType(0x03, fieldType, \"\")\n\t\t\tdata, err := testCase.Obj.MarshalUDT(field.Name, typeInfo)\n\t\t\tif field.Err {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, field.ValIn, data)\n\t\t\t}\n\t\t\tobj := testCase.New()\n\t\t\terr = obj.UnmarshalUDT(field.Name, typeInfo, field.ValIn)\n\t\t\tif field.Err {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/cassandra/gocql/testutils/udt_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage testutils_test\n\nimport (\n\t\"testing\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\n\tgocqlutils \"github.com/jaegertracing/jaeger/internal/storage/cassandra/gocql/testutils\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\n// CustomUDT is a custom type that implements gocql.UDTMarshaler and gocql.UDTUnmarshaler interfaces.\ntype CustomUDT struct {\n\tField1 int\n\tField2 string\n}\n\n// MarshalUDT implements the gocql.UDTMarshaler interface.\nfunc (c *CustomUDT) MarshalUDT(name string, info gocql.TypeInfo) ([]byte, error) {\n\tswitch name {\n\tcase \"Field1\":\n\t\treturn gocql.Marshal(info, c.Field1)\n\tcase \"Field2\":\n\t\treturn gocql.Marshal(info, c.Field2)\n\tdefault:\n\t\treturn nil, gocql.ErrNotFound\n\t}\n}\n\n// UnmarshalUDT implements the gocql.UDTUnmarshaler interface.\nfunc (c *CustomUDT) UnmarshalUDT(name string, info gocql.TypeInfo, data []byte) error {\n\tswitch name {\n\tcase \"Field1\":\n\t\treturn gocql.Unmarshal(info, data, &c.Field1)\n\tcase \"Field2\":\n\t\treturn gocql.Unmarshal(info, data, &c.Field2)\n\tdefault:\n\t\treturn gocql.ErrNotFound\n\t}\n}\n\nfunc TestUDTTestCase(t *testing.T) {\n\tudtInstance := &CustomUDT{\n\t\tField1: 1,\n\t\tField2: \"test\",\n\t}\n\n\t// Define UDT fields for testing\n\tudtFields := []gocqlutils.UDTField{\n\t\t{\n\t\t\tName:  \"Field1\",\n\t\t\tType:  gocql.TypeBigInt,\n\t\t\tValIn: []byte{0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\tErr:   false,\n\t\t},\n\t\t{\n\t\t\tName:  \"Field2\",\n\t\t\tType:  gocql.TypeVarchar,\n\t\t\tValIn: []byte(\"test\"),\n\t\t\tErr:   false,\n\t\t},\n\t\t{\n\t\t\tName:  \"InvalidField\",\n\t\t\tType:  gocql.TypeBigInt,\n\t\t\tValIn: []byte(\"test\"),\n\t\t\tErr:   true,\n\t\t},\n\t\t{\n\t\t\t// Type is omitted (zero value), should default to TypeVarchar\n\t\t\tName:  \"InvalidFieldType\",\n\t\t\tValIn: []byte(\"test\"),\n\t\t\tErr:   true,\n\t\t},\n\t}\n\n\t// Create a UDTTestCase\n\ttestCase := gocqlutils.UDTTestCase{\n\t\tObj:     udtInstance,\n\t\tObjName: \"CustomUDT\",\n\t\tNew:     func() gocql.UDTUnmarshaler { return &CustomUDT{} },\n\t\tFields:  udtFields,\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/metrics/table.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/spanstoremetrics\"\n)\n\n// Table is a collection of metrics about Cassandra write operations.\ntype Table struct {\n\tspanstoremetrics.WriteMetrics\n}\n\n// NewTable takes a metrics scope and creates a table metrics struct\nfunc NewTable(factory metrics.Factory, tableName string) *Table {\n\tt := spanstoremetrics.WriteMetrics{}\n\tmetrics.Init(&t, factory.Namespace(metrics.NSOptions{Name: \"\", Tags: map[string]string{\"table\": tableName}}), nil)\n\treturn &Table{t}\n}\n\n// Exec executes an update query and reports metrics/logs about it.\nfunc (t *Table) Exec(query cassandra.UpdateQuery, logger *zap.Logger) error {\n\tstart := time.Now()\n\terr := query.Exec()\n\tt.Emit(err, time.Since(start))\n\tif err != nil {\n\t\tqueryString := query.String()\n\t\tif logger != nil {\n\t\t\tlogger.Error(\"Failed to exec query\", zap.String(\"query\", queryString), zap.Error(err))\n\t\t}\n\t\treturn fmt.Errorf(\"failed to Exec query '%s': %w\", queryString, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/cassandra/metrics/table_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metrics\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestTableEmit(t *testing.T) {\n\ttestCases := []struct {\n\t\terr    error\n\t\tcounts map[string]int64\n\t\tgauges map[string]int64\n\t}{\n\t\t{\n\t\t\terr: nil,\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"attempts|table=a_table\": 1,\n\t\t\t\t\"inserts|table=a_table\":  1,\n\t\t\t},\n\t\t\tgauges: map[string]int64{\n\t\t\t\t\"latency-ok|table=a_table.P999\": 50,\n\t\t\t\t\"latency-ok|table=a_table.P50\":  50,\n\t\t\t\t\"latency-ok|table=a_table.P75\":  50,\n\t\t\t\t\"latency-ok|table=a_table.P90\":  50,\n\t\t\t\t\"latency-ok|table=a_table.P95\":  50,\n\t\t\t\t\"latency-ok|table=a_table.P99\":  50,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"some error\"),\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"attempts|table=a_table\": 1,\n\t\t\t\t\"errors|table=a_table\":   1,\n\t\t\t},\n\t\t\tgauges: map[string]int64{\n\t\t\t\t\"latency-err|table=a_table.P999\": 50,\n\t\t\t\t\"latency-err|table=a_table.P50\":  50,\n\t\t\t\t\"latency-err|table=a_table.P75\":  50,\n\t\t\t\t\"latency-err|table=a_table.P90\":  50,\n\t\t\t\t\"latency-err|table=a_table.P95\":  50,\n\t\t\t\t\"latency-err|table=a_table.P99\":  50,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tmf := metricstest.NewFactory(time.Second)\n\t\ttm := NewTable(mf, \"a_table\")\n\t\ttm.Emit(tc.err, 50*time.Millisecond)\n\t\tcounts, gauges := mf.Snapshot()\n\t\tassert.Equal(t, tc.counts, counts)\n\t\tassert.Equal(t, tc.gauges, gauges)\n\t\tmf.Stop()\n\t}\n}\n\nfunc TestTableExec(t *testing.T) {\n\ttestCases := []struct {\n\t\tq      insertQuery\n\t\tlog    bool\n\t\tcounts map[string]int64\n\t}{\n\t\t{\n\t\t\tq: insertQuery{},\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"attempts|table=a_table\": 1,\n\t\t\t\t\"inserts|table=a_table\":  1,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tq: insertQuery{\n\t\t\t\tstr: \"SELECT * FROM something\",\n\t\t\t\terr: errors.New(\"failed\"),\n\t\t\t},\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"attempts|table=a_table\": 1,\n\t\t\t\t\"errors|table=a_table\":   1,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tq: insertQuery{\n\t\t\t\tstr: \"SELECT * FROM something\",\n\t\t\t\terr: errors.New(\"failed\"),\n\t\t\t},\n\t\t\tlog: true,\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"attempts|table=a_table\": 1,\n\t\t\t\t\"errors|table=a_table\":   1,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tmf := metricstest.NewFactory(0)\n\t\ttm := NewTable(mf, \"a_table\")\n\t\tlogger, logBuf := testutils.NewLogger()\n\n\t\tuseLogger := logger\n\t\tif !tc.log {\n\t\t\tuseLogger = nil\n\t\t}\n\t\terr := tm.Exec(tc.q, useLogger)\n\t\tif tc.q.err == nil {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Empty(t, logBuf.Bytes())\n\t\t} else {\n\t\t\trequire.Error(t, err, tc.q.err.Error())\n\t\t\tif tc.log {\n\t\t\t\tassert.Equal(t, map[string]string{\n\t\t\t\t\t\"level\": \"error\",\n\t\t\t\t\t\"msg\":   \"Failed to exec query\",\n\t\t\t\t\t\"query\": \"SELECT * FROM something\",\n\t\t\t\t\t\"error\": \"failed\",\n\t\t\t\t}, logBuf.JSONLine(0))\n\t\t\t} else {\n\t\t\t\tassert.Empty(t, logBuf.Bytes())\n\t\t\t}\n\t\t}\n\t\tcounts, _ := mf.Snapshot()\n\t\tassert.Equal(t, tc.counts, counts)\n\t}\n}\n\ntype insertQuery struct {\n\terr error\n\tstr string\n}\n\nfunc (q insertQuery) Exec() error {\n\treturn q.err\n}\n\nfunc (q insertQuery) String() string {\n\treturn q.str\n}\n\nfunc (insertQuery) ScanCAS(...any /* dest */) (bool, error) {\n\treturn true, nil\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/cassandra/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewSession creates a new instance of Session. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSession(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Session {\n\tmock := &Session{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Session is an autogenerated mock type for the Session type\ntype Session struct {\n\tmock.Mock\n}\n\ntype Session_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Session) EXPECT() *Session_Expecter {\n\treturn &Session_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type Session\nfunc (_mock *Session) Close() {\n\t_mock.Called()\n\treturn\n}\n\n// Session_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype Session_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\nfunc (_e *Session_Expecter) Close() *Session_Close_Call {\n\treturn &Session_Close_Call{Call: _e.mock.On(\"Close\")}\n}\n\nfunc (_c *Session_Close_Call) Run(run func()) *Session_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Session_Close_Call) Return() *Session_Close_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *Session_Close_Call) RunAndReturn(run func()) *Session_Close_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// Query provides a mock function for the type Session\nfunc (_mock *Session) Query(stmt string, values ...any) cassandra.Query {\n\tvar tmpRet mock.Arguments\n\tif len(values) > 0 {\n\t\ttmpRet = _mock.Called(stmt, values)\n\t} else {\n\t\ttmpRet = _mock.Called(stmt)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Query\")\n\t}\n\n\tvar r0 cassandra.Query\n\tif returnFunc, ok := ret.Get(0).(func(string, ...any) cassandra.Query); ok {\n\t\tr0 = returnFunc(stmt, values...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(cassandra.Query)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Session_Query_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Query'\ntype Session_Query_Call struct {\n\t*mock.Call\n}\n\n// Query is a helper method to define mock.On call\n//   - stmt string\n//   - values ...any\nfunc (_e *Session_Expecter) Query(stmt interface{}, values ...interface{}) *Session_Query_Call {\n\treturn &Session_Query_Call{Call: _e.mock.On(\"Query\",\n\t\tappend([]interface{}{stmt}, values...)...)}\n}\n\nfunc (_c *Session_Query_Call) Run(run func(stmt string, values ...any)) *Session_Query_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\tvar arg1 []any\n\t\tvar variadicArgs []any\n\t\tif len(args) > 1 {\n\t\t\tvariadicArgs = args[1].([]any)\n\t\t}\n\t\targ1 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Session_Query_Call) Return(query cassandra.Query) *Session_Query_Call {\n\t_c.Call.Return(query)\n\treturn _c\n}\n\nfunc (_c *Session_Query_Call) RunAndReturn(run func(stmt string, values ...any) cassandra.Query) *Session_Query_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewQuery creates a new instance of Query. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewQuery(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Query {\n\tmock := &Query{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Query is an autogenerated mock type for the Query type\ntype Query struct {\n\tmock.Mock\n}\n\ntype Query_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Query) EXPECT() *Query_Expecter {\n\treturn &Query_Expecter{mock: &_m.Mock}\n}\n\n// Bind provides a mock function for the type Query\nfunc (_mock *Query) Bind(v ...any) cassandra.Query {\n\tvar tmpRet mock.Arguments\n\tif len(v) > 0 {\n\t\ttmpRet = _mock.Called(v)\n\t} else {\n\t\ttmpRet = _mock.Called()\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Bind\")\n\t}\n\n\tvar r0 cassandra.Query\n\tif returnFunc, ok := ret.Get(0).(func(...any) cassandra.Query); ok {\n\t\tr0 = returnFunc(v...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(cassandra.Query)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Query_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind'\ntype Query_Bind_Call struct {\n\t*mock.Call\n}\n\n// Bind is a helper method to define mock.On call\n//   - v ...any\nfunc (_e *Query_Expecter) Bind(v ...interface{}) *Query_Bind_Call {\n\treturn &Query_Bind_Call{Call: _e.mock.On(\"Bind\",\n\t\tappend([]interface{}{}, v...)...)}\n}\n\nfunc (_c *Query_Bind_Call) Run(run func(v ...any)) *Query_Bind_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []any\n\t\tvar variadicArgs []any\n\t\tif len(args) > 0 {\n\t\t\tvariadicArgs = args[0].([]any)\n\t\t}\n\t\targ0 = variadicArgs\n\t\trun(\n\t\t\targ0...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_Bind_Call) Return(query cassandra.Query) *Query_Bind_Call {\n\t_c.Call.Return(query)\n\treturn _c\n}\n\nfunc (_c *Query_Bind_Call) RunAndReturn(run func(v ...any) cassandra.Query) *Query_Bind_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Consistency provides a mock function for the type Query\nfunc (_mock *Query) Consistency(level cassandra.Consistency) cassandra.Query {\n\tret := _mock.Called(level)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Consistency\")\n\t}\n\n\tvar r0 cassandra.Query\n\tif returnFunc, ok := ret.Get(0).(func(cassandra.Consistency) cassandra.Query); ok {\n\t\tr0 = returnFunc(level)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(cassandra.Query)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Query_Consistency_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Consistency'\ntype Query_Consistency_Call struct {\n\t*mock.Call\n}\n\n// Consistency is a helper method to define mock.On call\n//   - level cassandra.Consistency\nfunc (_e *Query_Expecter) Consistency(level interface{}) *Query_Consistency_Call {\n\treturn &Query_Consistency_Call{Call: _e.mock.On(\"Consistency\", level)}\n}\n\nfunc (_c *Query_Consistency_Call) Run(run func(level cassandra.Consistency)) *Query_Consistency_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 cassandra.Consistency\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(cassandra.Consistency)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_Consistency_Call) Return(query cassandra.Query) *Query_Consistency_Call {\n\t_c.Call.Return(query)\n\treturn _c\n}\n\nfunc (_c *Query_Consistency_Call) RunAndReturn(run func(level cassandra.Consistency) cassandra.Query) *Query_Consistency_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Exec provides a mock function for the type Query\nfunc (_mock *Query) Exec() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Exec\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Query_Exec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exec'\ntype Query_Exec_Call struct {\n\t*mock.Call\n}\n\n// Exec is a helper method to define mock.On call\nfunc (_e *Query_Expecter) Exec() *Query_Exec_Call {\n\treturn &Query_Exec_Call{Call: _e.mock.On(\"Exec\")}\n}\n\nfunc (_c *Query_Exec_Call) Run(run func()) *Query_Exec_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_Exec_Call) Return(err error) *Query_Exec_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Query_Exec_Call) RunAndReturn(run func() error) *Query_Exec_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Iter provides a mock function for the type Query\nfunc (_mock *Query) Iter() cassandra.Iterator {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Iter\")\n\t}\n\n\tvar r0 cassandra.Iterator\n\tif returnFunc, ok := ret.Get(0).(func() cassandra.Iterator); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(cassandra.Iterator)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Query_Iter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Iter'\ntype Query_Iter_Call struct {\n\t*mock.Call\n}\n\n// Iter is a helper method to define mock.On call\nfunc (_e *Query_Expecter) Iter() *Query_Iter_Call {\n\treturn &Query_Iter_Call{Call: _e.mock.On(\"Iter\")}\n}\n\nfunc (_c *Query_Iter_Call) Run(run func()) *Query_Iter_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_Iter_Call) Return(iterator cassandra.Iterator) *Query_Iter_Call {\n\t_c.Call.Return(iterator)\n\treturn _c\n}\n\nfunc (_c *Query_Iter_Call) RunAndReturn(run func() cassandra.Iterator) *Query_Iter_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// PageSize provides a mock function for the type Query\nfunc (_mock *Query) PageSize(n int) cassandra.Query {\n\tret := _mock.Called(n)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for PageSize\")\n\t}\n\n\tvar r0 cassandra.Query\n\tif returnFunc, ok := ret.Get(0).(func(int) cassandra.Query); ok {\n\t\tr0 = returnFunc(n)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(cassandra.Query)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Query_PageSize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PageSize'\ntype Query_PageSize_Call struct {\n\t*mock.Call\n}\n\n// PageSize is a helper method to define mock.On call\n//   - n int\nfunc (_e *Query_Expecter) PageSize(n interface{}) *Query_PageSize_Call {\n\treturn &Query_PageSize_Call{Call: _e.mock.On(\"PageSize\", n)}\n}\n\nfunc (_c *Query_PageSize_Call) Run(run func(n int)) *Query_PageSize_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 int\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(int)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_PageSize_Call) Return(query cassandra.Query) *Query_PageSize_Call {\n\t_c.Call.Return(query)\n\treturn _c\n}\n\nfunc (_c *Query_PageSize_Call) RunAndReturn(run func(n int) cassandra.Query) *Query_PageSize_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// ScanCAS provides a mock function for the type Query\nfunc (_mock *Query) ScanCAS(dest ...any) (bool, error) {\n\tvar tmpRet mock.Arguments\n\tif len(dest) > 0 {\n\t\ttmpRet = _mock.Called(dest)\n\t} else {\n\t\ttmpRet = _mock.Called()\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for ScanCAS\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(...any) (bool, error)); ok {\n\t\treturn returnFunc(dest...)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(...any) bool); ok {\n\t\tr0 = returnFunc(dest...)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(...any) error); ok {\n\t\tr1 = returnFunc(dest...)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Query_ScanCAS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScanCAS'\ntype Query_ScanCAS_Call struct {\n\t*mock.Call\n}\n\n// ScanCAS is a helper method to define mock.On call\n//   - dest ...any\nfunc (_e *Query_Expecter) ScanCAS(dest ...interface{}) *Query_ScanCAS_Call {\n\treturn &Query_ScanCAS_Call{Call: _e.mock.On(\"ScanCAS\",\n\t\tappend([]interface{}{}, dest...)...)}\n}\n\nfunc (_c *Query_ScanCAS_Call) Run(run func(dest ...any)) *Query_ScanCAS_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []any\n\t\tvar variadicArgs []any\n\t\tif len(args) > 0 {\n\t\t\tvariadicArgs = args[0].([]any)\n\t\t}\n\t\targ0 = variadicArgs\n\t\trun(\n\t\t\targ0...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_ScanCAS_Call) Return(b bool, err error) *Query_ScanCAS_Call {\n\t_c.Call.Return(b, err)\n\treturn _c\n}\n\nfunc (_c *Query_ScanCAS_Call) RunAndReturn(run func(dest ...any) (bool, error)) *Query_ScanCAS_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// String provides a mock function for the type Query\nfunc (_mock *Query) String() string {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for String\")\n\t}\n\n\tvar r0 string\n\tif returnFunc, ok := ret.Get(0).(func() string); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Get(0).(string)\n\t}\n\treturn r0\n}\n\n// Query_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String'\ntype Query_String_Call struct {\n\t*mock.Call\n}\n\n// String is a helper method to define mock.On call\nfunc (_e *Query_Expecter) String() *Query_String_Call {\n\treturn &Query_String_Call{Call: _e.mock.On(\"String\")}\n}\n\nfunc (_c *Query_String_Call) Run(run func()) *Query_String_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Query_String_Call) Return(s string) *Query_String_Call {\n\t_c.Call.Return(s)\n\treturn _c\n}\n\nfunc (_c *Query_String_Call) RunAndReturn(run func() string) *Query_String_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewIterator creates a new instance of Iterator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIterator(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Iterator {\n\tmock := &Iterator{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Iterator is an autogenerated mock type for the Iterator type\ntype Iterator struct {\n\tmock.Mock\n}\n\ntype Iterator_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Iterator) EXPECT() *Iterator_Expecter {\n\treturn &Iterator_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type Iterator\nfunc (_mock *Iterator) Close() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Close\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Iterator_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype Iterator_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\nfunc (_e *Iterator_Expecter) Close() *Iterator_Close_Call {\n\treturn &Iterator_Close_Call{Call: _e.mock.On(\"Close\")}\n}\n\nfunc (_c *Iterator_Close_Call) Run(run func()) *Iterator_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Iterator_Close_Call) Return(err error) *Iterator_Close_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Iterator_Close_Call) RunAndReturn(run func() error) *Iterator_Close_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Scan provides a mock function for the type Iterator\nfunc (_mock *Iterator) Scan(dest ...any) bool {\n\tvar tmpRet mock.Arguments\n\tif len(dest) > 0 {\n\t\ttmpRet = _mock.Called(dest)\n\t} else {\n\t\ttmpRet = _mock.Called()\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Scan\")\n\t}\n\n\tvar r0 bool\n\tif returnFunc, ok := ret.Get(0).(func(...any) bool); ok {\n\t\tr0 = returnFunc(dest...)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\treturn r0\n}\n\n// Iterator_Scan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Scan'\ntype Iterator_Scan_Call struct {\n\t*mock.Call\n}\n\n// Scan is a helper method to define mock.On call\n//   - dest ...any\nfunc (_e *Iterator_Expecter) Scan(dest ...interface{}) *Iterator_Scan_Call {\n\treturn &Iterator_Scan_Call{Call: _e.mock.On(\"Scan\",\n\t\tappend([]interface{}{}, dest...)...)}\n}\n\nfunc (_c *Iterator_Scan_Call) Run(run func(dest ...any)) *Iterator_Scan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []any\n\t\tvar variadicArgs []any\n\t\tif len(args) > 0 {\n\t\t\tvariadicArgs = args[0].([]any)\n\t\t}\n\t\targ0 = variadicArgs\n\t\trun(\n\t\t\targ0...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Iterator_Scan_Call) Return(b bool) *Iterator_Scan_Call {\n\t_c.Call.Return(b)\n\treturn _c\n}\n\nfunc (_c *Iterator_Scan_Call) RunAndReturn(run func(dest ...any) bool) *Iterator_Scan_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/cassandra/session.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\n// Consistency is Cassandra's consistency level for queries.\ntype Consistency uint16\n\nconst (\n\t// Any ...\n\tAny Consistency = 0x00\n\t// One ...\n\tOne Consistency = 0x01\n\t// Two ...\n\tTwo Consistency = 0x02\n\t// Three ...\n\tThree Consistency = 0x03\n\t// Quorum ...\n\tQuorum Consistency = 0x04\n\t// All ...\n\tAll Consistency = 0x05\n\t// LocalQuorum ...\n\tLocalQuorum Consistency = 0x06\n\t// EachQuorum ...\n\tEachQuorum Consistency = 0x07\n\t// LocalOne ...\n\tLocalOne Consistency = 0x0A\n)\n\n// Session is an abstraction of gocql.Session\ntype Session interface {\n\tQuery(stmt string, values ...any) Query\n\tClose()\n}\n\n// UpdateQuery is a subset of Query just for updates\ntype UpdateQuery interface {\n\tExec() error\n\tString() string\n\n\t// ScanCAS executes a lightweight transaction (i.e. an UPDATE or INSERT\n\t// statement containing an IF clause). If the transaction fails because\n\t// the existing values did not match, the previous values will be stored\n\t// in dest.\n\tScanCAS(dest ...any) (bool, error)\n}\n\n// Query is an abstraction of gocql.Query\ntype Query interface {\n\tUpdateQuery\n\tIter() Iterator\n\tBind(v ...any) Query\n\tConsistency(level Consistency) Query\n\tPageSize(int) Query\n}\n\n// Iterator is an abstraction of gocql.Iter\ntype Iterator interface {\n\tScan(dest ...any) bool\n\tClose() error\n}\n"
  },
  {
    "path": "internal/storage/distributedlock/cassandra/lock.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n)\n\n// Lock is a distributed lock based off Cassandra.\ntype Lock struct {\n\tsession  cassandra.Session\n\ttenantID string\n}\n\nconst (\n\tdefaultTTL = 60 * time.Second\n\n\tleasesTable   = `leases`\n\tcqlInsertLock = `INSERT INTO ` + leasesTable + ` (name, owner) VALUES (?,?) IF NOT EXISTS USING TTL ?;`\n\tcqlUpdateLock = `UPDATE ` + leasesTable + ` USING TTL ? SET owner = ? WHERE name = ? IF owner = ?;`\n\tcqlDeleteLock = `DELETE FROM ` + leasesTable + ` WHERE name = ? IF owner = ?;`\n)\n\nvar errLockOwnership = errors.New(\"this host does not own the resource lock\")\n\n// NewLock creates a new instance of a distributed locking mechanism based off Cassandra.\nfunc NewLock(session cassandra.Session, tenantID string) *Lock {\n\treturn &Lock{\n\t\tsession:  session,\n\t\ttenantID: tenantID,\n\t}\n}\n\n// Acquire acquires a lease around a given resource. NB. Cassandra only allows ttl of seconds granularity\nfunc (l *Lock) Acquire(resource string, ttl time.Duration) (bool, error) {\n\tif ttl == 0 {\n\t\tttl = defaultTTL\n\t}\n\tttlSec := int(ttl.Seconds())\n\tvar name, owner string\n\tapplied, err := l.session.Query(cqlInsertLock, resource, l.tenantID, ttlSec).ScanCAS(&name, &owner)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to acquire resource lock due to cassandra error: %w\", err)\n\t}\n\tif applied {\n\t\t// The lock was successfully created\n\t\treturn true, nil\n\t}\n\tif owner == l.tenantID {\n\t\t// This host already owns the lock, extend the lease\n\t\tif err = l.extendLease(resource, ttl); err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to extend lease on resource lock: %w\", err)\n\t\t}\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\n// Forfeit forfeits an existing lease around a given resource.\nfunc (l *Lock) Forfeit(resource string) (bool, error) {\n\tvar name, owner string\n\tapplied, err := l.session.Query(cqlDeleteLock, resource, l.tenantID).ScanCAS(&name, &owner)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to forfeit resource lock due to cassandra error: %w\", err)\n\t}\n\tif applied {\n\t\t// The lock was successfully deleted\n\t\treturn true, nil\n\t}\n\treturn false, fmt.Errorf(\"failed to forfeit resource lock: %w\", errLockOwnership)\n}\n\n// extendLease will attempt to extend the lease of an existing lock on a given resource.\nfunc (l *Lock) extendLease(resource string, ttl time.Duration) error {\n\tttlSec := int(ttl.Seconds())\n\tvar owner string\n\tapplied, err := l.session.Query(cqlUpdateLock, ttlSec, l.tenantID, resource, l.tenantID).ScanCAS(&owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif applied {\n\t\treturn nil\n\t}\n\treturn errLockOwnership\n}\n"
  },
  {
    "path": "internal/storage/distributedlock/cassandra/lock_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nvar (\n\tlocalhost    = \"localhost\"\n\tsamplingLock = \"sampling_lock\"\n)\n\ntype cqlLockTest struct {\n\tsession *mocks.Session\n\tlock    *Lock\n}\n\nfunc withCQLLock(fn func(r *cqlLockTest)) {\n\tsession := &mocks.Session{}\n\tr := &cqlLockTest{\n\t\tsession: session,\n\t\tlock:    NewLock(session, localhost),\n\t}\n\tfn(r)\n}\n\nfunc TestExtendLease(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption        string\n\t\tapplied        bool\n\t\terrScan        error\n\t\texpectedErrMsg string\n\t}{\n\t\t{\n\t\t\tcaption:        \"cassandra error\",\n\t\t\tapplied:        false,\n\t\t\terrScan:        errors.New(\"Failed to update lock\"),\n\t\t\texpectedErrMsg: \"Failed to update lock\",\n\t\t},\n\t\t{\n\t\t\tcaption:        \"successfully extended lease\",\n\t\t\tapplied:        true,\n\t\t\terrScan:        nil,\n\t\t\texpectedErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tcaption:        \"failed to extend lease\",\n\t\t\tapplied:        false,\n\t\t\terrScan:        nil,\n\t\t\texpectedErrMsg: \"this host does not own the resource lock\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithCQLLock(func(s *cqlLockTest) {\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"ScanCAS\", mock.Anything).Return(testCase.applied, testCase.errScan)\n\n\t\t\t\ts.session.On(\n\t\t\t\t\t\"Query\",\n\t\t\t\t\tmock.AnythingOfType(\"string\"),\n\t\t\t\t\t[]any{\n\t\t\t\t\t\t60,\n\t\t\t\t\t\tlocalhost,\n\t\t\t\t\t\tsamplingLock,\n\t\t\t\t\t\tlocalhost,\n\t\t\t\t\t},\n\t\t\t\t).Return(query)\n\t\t\t\terr := s.lock.extendLease(samplingLock, time.Second*60)\n\t\t\t\tif testCase.expectedErrMsg == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedErrMsg)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestAcquire(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption           string\n\t\tinsertLockApplied bool\n\t\tretVals           []string\n\t\tacquired          bool\n\t\tupdateLockApplied bool\n\t\terrScan           error\n\t\texpectedErrMsg    string\n\t}{\n\t\t{\n\t\t\tcaption:           \"cassandra error\",\n\t\t\tinsertLockApplied: false,\n\t\t\tretVals:           []string{\"\", \"\"},\n\t\t\tacquired:          false,\n\t\t\terrScan:           errors.New(\"Failed to create lock\"),\n\t\t\texpectedErrMsg:    \"failed to acquire resource lock due to cassandra error: Failed to create lock\",\n\t\t},\n\t\t{\n\t\t\tcaption:           \"successfully created lock\",\n\t\t\tinsertLockApplied: true,\n\t\t\tacquired:          true,\n\t\t\tretVals:           []string{samplingLock, localhost},\n\t\t\terrScan:           nil,\n\t\t\texpectedErrMsg:    \"\",\n\t\t},\n\t\t{\n\t\t\tcaption:           \"lock already exists and belongs to localhost\",\n\t\t\tinsertLockApplied: false,\n\t\t\tacquired:          true,\n\t\t\tretVals:           []string{samplingLock, localhost},\n\t\t\tupdateLockApplied: true,\n\t\t\terrScan:           nil,\n\t\t\texpectedErrMsg:    \"\",\n\t\t},\n\t\t{\n\t\t\tcaption:           \"lock already exists and belongs to localhost but is lost\",\n\t\t\tinsertLockApplied: false,\n\t\t\tacquired:          false,\n\t\t\tretVals:           []string{samplingLock, localhost},\n\t\t\tupdateLockApplied: false,\n\t\t\terrScan:           nil,\n\t\t\texpectedErrMsg:    \"failed to extend lease on resource lock: this host does not own the resource lock\",\n\t\t},\n\t\t{\n\t\t\tcaption:           \"failed to acquire lock\",\n\t\t\tinsertLockApplied: false,\n\t\t\tacquired:          false,\n\t\t\tretVals:           []string{samplingLock, \"otherhost\"},\n\t\t\terrScan:           nil,\n\t\t\texpectedErrMsg:    \"\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithCQLLock(func(s *cqlLockTest) {\n\t\t\t\tassignPtr := func(vals ...string) any {\n\t\t\t\t\treturn mock.MatchedBy(func(args []any) bool {\n\t\t\t\t\t\tif len(args) != len(vals) {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor i, arg := range args {\n\t\t\t\t\t\t\tptr, ok := arg.(*string)\n\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t*ptr = vals[i]\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tfirstQuery := &mocks.Query{}\n\t\t\t\tfirstQuery.On(\"ScanCAS\", assignPtr(testCase.retVals...)).\n\t\t\t\t\tReturn(testCase.insertLockApplied, testCase.errScan)\n\t\t\t\tsecondQuery := &mocks.Query{}\n\t\t\t\tsecondQuery.On(\"ScanCAS\", mock.Anything).Return(testCase.updateLockApplied, nil)\n\n\t\t\t\ts.session.On(\"Query\", stringMatcher(\"INSERT INTO leases\"), mock.Anything).Return(firstQuery)\n\t\t\t\ts.session.On(\"Query\", stringMatcher(\"UPDATE leases\"), mock.Anything).Return(secondQuery)\n\t\t\t\tacquired, err := s.lock.Acquire(samplingLock, 0)\n\t\t\t\tif testCase.expectedErrMsg == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedErrMsg)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, testCase.acquired, acquired)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestForfeit(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption        string\n\t\tapplied        bool\n\t\tretVals        []string\n\t\terrScan        error\n\t\texpectedErrMsg string\n\t}{\n\t\t{\n\t\t\tcaption:        \"cassandra error\",\n\t\t\tapplied:        false,\n\t\t\tretVals:        []string{\"\", \"\"},\n\t\t\terrScan:        errors.New(\"Failed to delete lock\"),\n\t\t\texpectedErrMsg: \"failed to forfeit resource lock due to cassandra error: Failed to delete lock\",\n\t\t},\n\t\t{\n\t\t\tcaption:        \"successfully forfeited lock\",\n\t\t\tapplied:        true,\n\t\t\tretVals:        []string{samplingLock, localhost},\n\t\t\terrScan:        nil,\n\t\t\texpectedErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tcaption:        \"failed to delete lock\",\n\t\t\tapplied:        false,\n\t\t\tretVals:        []string{samplingLock, \"otherhost\"},\n\t\t\terrScan:        nil,\n\t\t\texpectedErrMsg: \"failed to forfeit resource lock: this host does not own the resource lock\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithCQLLock(func(s *cqlLockTest) {\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"ScanCAS\", mock.Anything).Return(testCase.applied, testCase.errScan)\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), []any{samplingLock, localhost}).Return(query)\n\t\t\t\tapplied, err := s.lock.Forfeit(samplingLock)\n\t\t\t\tif testCase.expectedErrMsg == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedErrMsg)\n\t\t\t\t}\n\t\t\t\tassert.Equal(t, testCase.applied, applied)\n\t\t\t})\n\t\t})\n\t}\n}\n\n// stringMatcher can match a string argument when it contains a specific substring q\nfunc stringMatcher(q string) any {\n\tmatchFunc := func(s string) bool {\n\t\treturn strings.Contains(s, q)\n\t}\n\treturn mock.MatchedBy(matchFunc)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/basic_auth.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport \"encoding/base64\"\n\n// BasicAuth encode username and password to be used with basic authentication header\nfunc BasicAuth(username, password string) string {\n\tif username == \"\" || password == \"\" {\n\t\treturn \"\"\n\t}\n\treturn base64.StdEncoding.EncodeToString([]byte(username + \":\" + password))\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/basic_auth_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBasicAuth(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tusername       string\n\t\tpassword       string\n\t\texpectedResult string\n\t}{\n\t\t{\n\t\t\tname:           \"user and password\",\n\t\t\tusername:       \"admin\",\n\t\t\tpassword:       \"qwerty123456\",\n\t\t\texpectedResult: \"YWRtaW46cXdlcnR5MTIzNDU2\",\n\t\t},\n\t\t{\n\t\t\tname:           \"username empty\",\n\t\t\tusername:       \"\",\n\t\t\tpassword:       \"qwerty123456\",\n\t\t\texpectedResult: \"\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tresult := BasicAuth(test.username, test.password)\n\t\t\tassert.Equal(t, test.expectedResult, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/client.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\n// ResponseError holds information about a request error\ntype ResponseError struct {\n\t// Error returned by the http client\n\tErr error\n\t// StatusCode is the http code returned by the server (if any)\n\tStatusCode int\n\t// Body is the bytes readed in the response (if any)\n\tBody []byte\n}\n\n// Error returns the error string of the Err field\nfunc (r ResponseError) Error() string {\n\treturn r.Err.Error()\n}\n\nfunc (r ResponseError) prefixMessage(message string) ResponseError {\n\treturn ResponseError{\n\t\tErr:        fmt.Errorf(\"%s, %w\", message, r.Err),\n\t\tStatusCode: r.StatusCode,\n\t\tBody:       r.Body,\n\t}\n}\n\nfunc newResponseError(err error, code int, body []byte) ResponseError {\n\treturn ResponseError{\n\t\tErr:        err,\n\t\tStatusCode: code,\n\t\tBody:       body,\n\t}\n}\n\n// Client executes requests against Elasticsearch using direct HTTP calls,\n// without using the official Go client for ES.\ntype Client struct {\n\t// Http client.\n\tClient *http.Client\n\t// ES server endpoint.\n\tEndpoint string\n\t// Basic authentication string.\n\tBasicAuth string\n}\n\ntype elasticRequest struct {\n\tendpoint string\n\tbody     []byte\n\tmethod   string\n}\n\nfunc (c *Client) request(esRequest elasticRequest) ([]byte, error) {\n\tvar reader *bytes.Buffer\n\tvar r *http.Request\n\tvar err error\n\tif len(esRequest.body) > 0 {\n\t\treader = bytes.NewBuffer(esRequest.body)\n\t\tr, err = http.NewRequestWithContext(context.Background(), esRequest.method, fmt.Sprintf(\"%s/%s\", c.Endpoint, esRequest.endpoint), reader)\n\t} else {\n\t\tr, err = http.NewRequestWithContext(context.Background(), esRequest.method, fmt.Sprintf(\"%s/%s\", c.Endpoint, esRequest.endpoint), http.NoBody)\n\t}\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\tc.setAuthorization(r)\n\tr.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := c.Client.Do(r) //nolint:gosec // G704 - URL from ES config\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode != http.StatusOK {\n\t\treturn []byte{}, c.handleFailedRequest(res)\n\t}\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treturn body, nil\n}\n\nfunc (c *Client) setAuthorization(r *http.Request) {\n\tif c.BasicAuth != \"\" {\n\t\tr.Header.Add(\"Authorization\", \"Basic \"+c.BasicAuth)\n\t}\n}\n\nfunc (*Client) handleFailedRequest(res *http.Response) error {\n\tif res.Body != nil {\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn newResponseError(fmt.Errorf(\"request failed and failed to read response body, status code: %d, %w\", res.StatusCode, err), res.StatusCode, nil)\n\t\t}\n\t\tbody := string(bodyBytes)\n\t\treturn newResponseError(fmt.Errorf(\"request failed, status code: %d, body: %s\", res.StatusCode, body), res.StatusCode, bodyBytes)\n\t}\n\treturn newResponseError(fmt.Errorf(\"request failed, status code: %d\", res.StatusCode), res.StatusCode, nil)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/cluster_client.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar _ ClusterAPI = (*ClusterClient)(nil)\n\n// ClusterClient is a client used to get ES cluster information\ntype ClusterClient struct {\n\tClient\n}\n\n// Version returns the major version of the ES cluster\nfunc (c *ClusterClient) Version() (uint, error) {\n\ttype clusterInfo struct {\n\t\tVersion map[string]any `json:\"version\"`\n\t\tTagLine string         `json:\"tagline\"`\n\t}\n\tbody, err := c.request(elasticRequest{\n\t\tendpoint: \"\",\n\t\tmethod:   http.MethodGet,\n\t})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tvar info clusterInfo\n\terr = json.Unmarshal(body, &info)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tversionField := info.Version[\"number\"]\n\tversionNumber, isString := versionField.(string)\n\tif !isString {\n\t\treturn 0, fmt.Errorf(\"invalid version format: %v\", versionField)\n\t}\n\tversion := strings.Split(versionNumber, \".\")\n\tmajor, err := strconv.ParseUint(version[0], 10, 32)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"invalid version format: %s\", version[0])\n\t}\n\tif strings.Contains(info.TagLine, \"OpenSearch\") && (major == 1 || major == 2 || major == 3) {\n\t\treturn 7, nil\n\t}\n\treturn uint(major), nil\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/cluster_client_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst badVersionType = `\n{\n\t\"name\" : \"opensearch-node1\",\n\t\"cluster_name\" : \"opensearch-cluster\",\n\t\"cluster_uuid\" : \"1StaUGrGSx61r41d-1nDiw\",\n\t\"version\" : {\n\t  \"distribution\" : \"opensearch\",\n\t  \"number\" : true,\n\t  \"build_type\" : \"tar\",\n\t  \"build_hash\" : \"34550c5b17124ddc59458ef774f6b43a086522e3\",\n\t  \"build_date\" : \"2021-07-02T23:22:21.383695Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"8.8.2\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"The OpenSearch Project: https://opensearch.org/\"\n  }\n`\n\nconst badVersionNoNumber = `\n{\n\t\"name\" : \"opensearch-node1\",\n\t\"cluster_name\" : \"opensearch-cluster\",\n\t\"cluster_uuid\" : \"1StaUGrGSx61r41d-1nDiw\",\n\t\"version\" : {\n\t  \"distribution\" : \"opensearch\",\n\t  \"number\" : \"thisisnotanumber\",\n\t  \"build_type\" : \"tar\",\n\t  \"build_hash\" : \"34550c5b17124ddc59458ef774f6b43a086522e3\",\n\t  \"build_date\" : \"2021-07-02T23:22:21.383695Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"8.8.2\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"The OpenSearch Project: https://opensearch.org/\"\n  }\n`\n\nconst opensearch1 = `\n{\n\t\"name\" : \"opensearch-node1\",\n\t\"cluster_name\" : \"opensearch-cluster\",\n\t\"cluster_uuid\" : \"1StaUGrGSx61r41d-1nDiw\",\n\t\"version\" : {\n\t  \"distribution\" : \"opensearch\",\n\t  \"number\" : \"1.0.0\",\n\t  \"build_type\" : \"tar\",\n\t  \"build_hash\" : \"34550c5b17124ddc59458ef774f6b43a086522e3\",\n\t  \"build_date\" : \"2021-07-02T23:22:21.383695Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"8.8.2\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"The OpenSearch Project: https://opensearch.org/\"\n  }\n`\n\nconst opensearch2 = `\n{\n\t\"name\" : \"opensearch-node1\",\n\t\"cluster_name\" : \"opensearch-cluster\",\n\t\"cluster_uuid\" : \"1StaUGrGSx61r41d-1nDiw\",\n\t\"version\" : {\n\t  \"distribution\" : \"opensearch\",\n\t  \"number\" : \"2.3.0\",\n\t  \"build_type\" : \"tar\",\n\t  \"build_hash\" : \"34550c5b17124ddc59458ef774f6b43a086522e3\",\n\t  \"build_date\" : \"2021-07-02T23:22:21.383695Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"8.8.2\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"The OpenSearch Project: https://opensearch.org/\"\n  }\n`\n\nconst opensearch3 = `\n{\n\t\"name\" : \"opensearch-node1\",\n\t\"cluster_name\" : \"opensearch-cluster\",\n\t\"cluster_uuid\" : \"1StaUGrGSx61r41d-1nDiw\",\n\t\"version\" : {\n\t  \"distribution\" : \"opensearch\",\n\t  \"number\" : \"3.0.0\",\n\t  \"build_type\" : \"tar\",\n\t  \"build_hash\" : \"34550c5b17124ddc59458ef774f6b43a086522e3\",\n\t  \"build_date\" : \"2025-05-06T23:22:21.383695Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"10.0.0\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"The OpenSearch Project: https://opensearch.org/\"\n  }\n`\n\nconst elasticsearch7 = `\n{\n\t\"name\" : \"elasticsearch-0\",\n\t\"cluster_name\" : \"clustername\",\n\t\"cluster_uuid\" : \"HUtdg7bRTomSFaOk7Wzt8w\",\n\t\"version\" : {\n\t  \"number\" : \"7.6.1\",\n\t  \"build_flavor\" : \"default\",\n\t  \"build_type\" : \"docker\",\n\t  \"build_hash\" : \"aa751e09be0a5072e8570670309b1f12348f023b\",\n\t  \"build_date\" : \"2020-02-29T00:15:25.529771Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"8.4.0\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"You Know, for Search\"\n  }\n`\n\nconst elasticsearch8 = `\n{\n\t\"name\" : \"elasticsearch-0\",\n\t\"version\" : {\n\t  \"number\" : \"8.0.0\"\n\t},\n\t\"tagline\" : \"You Know, for Search\"\n  }\n`\n\nconst elasticsearch6 = `\n{\n\t\"name\" : \"elasticsearch-0\",\n\t\"cluster_name\" : \"clustername\",\n\t\"cluster_uuid\" : \"HUtdg7bRTomSFaOk7Wzt8w\",\n\t\"version\" : {\n\t  \"number\" : \"6.8.0\",\n\t  \"build_flavor\" : \"default\",\n\t  \"build_type\" : \"docker\",\n\t  \"build_hash\" : \"aa751e09be0a5072e8570670309b1f12348f023b\",\n\t  \"build_date\" : \"2020-02-29T00:15:25.529771Z\",\n\t  \"build_snapshot\" : false,\n\t  \"lucene_version\" : \"8.4.0\",\n\t  \"minimum_wire_compatibility_version\" : \"6.8.0\",\n\t  \"minimum_index_compatibility_version\" : \"6.0.0-beta1\"\n\t},\n\t\"tagline\" : \"You Know, for Search\"\n  }\n`\n\nfunc TestVersion(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tresponseCode   int\n\t\tresponse       string\n\t\terrContains    string\n\t\texpectedResult uint\n\t}{\n\t\t{\n\t\t\tname:           \"success with elasticsearch 6\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\tresponse:       elasticsearch6,\n\t\t\texpectedResult: 6,\n\t\t},\n\t\t{\n\t\t\tname:           \"success with elasticsearch 7\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\tresponse:       elasticsearch7,\n\t\t\texpectedResult: 7,\n\t\t},\n\t\t{\n\t\t\tname:           \"success with elasticsearch 8\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\tresponse:       elasticsearch8,\n\t\t\texpectedResult: 8,\n\t\t},\n\t\t{\n\t\t\tname:           \"success with opensearch 1\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\tresponse:       opensearch1,\n\t\t\texpectedResult: 7,\n\t\t},\n\t\t{\n\t\t\tname:           \"success with opensearch 2\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\tresponse:       opensearch2,\n\t\t\texpectedResult: 7,\n\t\t},\n\t\t{\n\t\t\tname:           \"success with opensearch 3\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\tresponse:       opensearch3,\n\t\t\texpectedResult: 7,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"request failed, status code: 400\",\n\t\t},\n\t\t{\n\t\t\tname:         \"bad version\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     badVersionType,\n\t\t\terrContains:  \"invalid version format: true\",\n\t\t},\n\t\t{\n\t\t\tname:         \"version not a number\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     badVersionNoNumber,\n\t\t\terrContains:  \"invalid version format: thisisnotanumber\",\n\t\t},\n\t\t{\n\t\t\tname:         \"unmarshal error\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     \"thisisaninvalidjson\",\n\t\t\terrContains:  \"invalid character\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodGet, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &ClusterClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tresult, err := c.Version()\n\t\t\tif test.errContains != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.errContains)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expectedResult, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/ilm_client.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v5\"\n\t\"go.uber.org/zap\"\n)\n\nconst (\n\tmaxTries       = 3\n\tmaxElapsedTime = 5 * time.Second\n)\n\nvar _ IndexManagementLifecycleAPI = (*ILMClient)(nil)\n\n// ILMClient is a client used to manipulate Index lifecycle management policies.\ntype ILMClient struct {\n\tClient\n\tMasterTimeoutSeconds int\n\tLogger               *zap.Logger\n}\n\n// Exists verify if a ILM policy exists\nfunc (i ILMClient) Exists(name string) (bool, error) {\n\toperation := func() ([]byte, error) {\n\t\tbytes, err := i.request(elasticRequest{\n\t\t\tendpoint: \"_ilm/policy/\" + name,\n\t\t\tmethod:   http.MethodGet,\n\t\t})\n\t\tif err != nil {\n\t\t\ti.Logger.Warn(\"Retryable error while getting ILM policy\",\n\t\t\t\tzap.String(\"name\", name),\n\t\t\t\tzap.Error(err),\n\t\t\t)\n\t\t\treturn bytes, err\n\t\t}\n\t\treturn bytes, nil\n\t}\n\n\t_, err := backoff.Retry(\n\t\tcontext.TODO(),\n\t\toperation,\n\t\tbackoff.WithMaxTries(maxTries),\n\t\tbackoff.WithMaxElapsedTime(maxElapsedTime),\n\t)\n\n\tvar respError ResponseError\n\tif errors.As(err, &respError) {\n\t\tif respError.StatusCode == http.StatusNotFound {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to get ILM policy: %s, %w\", name, err)\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/ilm_client_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestExists(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname           string\n\t\tresponseCode   int\n\t\tresponse       string\n\t\terrContains    string\n\t\texpectedResult bool\n\t}{\n\t\t{\n\t\t\tname:           \"found\",\n\t\t\tresponseCode:   http.StatusOK,\n\t\t\texpectedResult: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"not found\",\n\t\t\tresponseCode: http.StatusNotFound,\n\t\t\tresponse:     esErrResponse,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to get ILM policy: jaeger-ilm-policy\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"_ilm/policy/jaeger-ilm-policy\"))\n\t\t\t\tassert.Equal(t, http.MethodGet, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &ILMClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t\tLogger: zap.NewNop(),\n\t\t\t}\n\t\t\tresult, err := c.Exists(\"jaeger-ilm-policy\")\n\t\t\tif test.errContains != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.errContains)\n\t\t\t}\n\t\t\tassert.Equal(t, test.expectedResult, result)\n\t\t})\n\t}\n}\n\nfunc TestExists_Retries(t *testing.T) {\n\tt.Parallel()\n\tvar callCount int\n\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tcallCount++\n\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"_ilm/policy/jaeger-ilm-policy\"))\n\t\tassert.Equal(t, http.MethodGet, req.Method)\n\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\n\t\tif callCount < maxTries {\n\t\t\tres.WriteHeader(http.StatusInternalServerError)\n\t\t\tres.Write([]byte(`{\"error\": \"server error\"}`))\n\t\t\treturn\n\t\t}\n\n\t\tres.WriteHeader(http.StatusOK)\n\t}))\n\tdefer testServer.Close()\n\n\tc := &ILMClient{\n\t\tClient: Client{\n\t\t\tClient:    testServer.Client(),\n\t\t\tEndpoint:  testServer.URL,\n\t\t\tBasicAuth: \"foobar\",\n\t\t},\n\t\tLogger: zap.NewNop(),\n\t}\n\n\tresult, err := c.Exists(\"jaeger-ilm-policy\")\n\trequire.NoError(t, err)\n\tassert.True(t, result)\n\tassert.Equal(t, maxTries, callCount, \"should retry twice before succeeding\")\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/index_client.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Index represents ES index.\ntype Index struct {\n\t// Index name.\n\tIndex string\n\t// Index creation time.\n\tCreationTime time.Time\n\t// Aliases\n\tAliases map[string]bool\n}\n\n// Alias represents ES alias.\ntype Alias struct {\n\t// Index name.\n\tIndex string\n\t// Alias name.\n\tName string\n\t// IsWriteIndex option\n\tIsWriteIndex bool\n}\n\nvar _ IndexAPI = (*IndicesClient)(nil)\n\n// IndicesClient is a client used to manipulate indices.\ntype IndicesClient struct {\n\tClient\n\tMasterTimeoutSeconds   int\n\tIgnoreUnavailableIndex bool\n}\n\n// GetJaegerIndices queries all Jaeger indices including the archive and rollover.\n// Jaeger daily indices are:\n// - jaeger-span-2019-01-01\n// - jaeger-service-2019-01-01\n// - jaeger-dependencies-2019-01-01\n// - jaeger-span-archive\n//\n// Rollover indices:\n// - aliases: jaeger-span-read, jaeger-span-write, jaeger-service-read, jaeger-service-write\n// - indices: jaeger-span-000001, jaeger-service-000001 etc.\n// - aliases: jaeger-span-archive-read, jaeger-span-archive-write\n// - indices: jaeger-span-archive-000001\nfunc (i *IndicesClient) GetJaegerIndices(prefix string) ([]Index, error) {\n\tprefix += \"jaeger-*\"\n\n\tbody, err := i.request(elasticRequest{\n\t\tendpoint: prefix + \"?flat_settings=true&filter_path=*.aliases,*.settings\",\n\t\tmethod:   http.MethodGet,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query indices: %w\", err)\n\t}\n\n\ttype indexInfo struct {\n\t\tAliases  map[string]any    `json:\"aliases\"`\n\t\tSettings map[string]string `json:\"settings\"`\n\t}\n\tvar indicesInfo map[string]indexInfo\n\tif err = json.Unmarshal(body, &indicesInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query indices and unmarshall response body: %q: %w\", body, err)\n\t}\n\n\tvar indices []Index\n\tfor k, v := range indicesInfo {\n\t\taliases := map[string]bool{}\n\t\tfor alias := range v.Aliases {\n\t\t\taliases[alias] = true\n\t\t}\n\t\t// ignoring error, ES should return valid date\n\t\tcreationDate, _ := strconv.ParseInt(v.Settings[\"index.creation_date\"], 10, 64)\n\n\t\tindices = append(indices, Index{\n\t\t\tIndex:        k,\n\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*creationDate),\n\t\t\tAliases:      aliases,\n\t\t})\n\t}\n\treturn indices, nil\n}\n\n// execute delete request\nfunc (i *IndicesClient) indexDeleteRequest(concatIndices string) error {\n\t_, err := i.request(elasticRequest{\n\t\tendpoint: fmt.Sprintf(\"%s?master_timeout=%ds&ignore_unavailable=%t\", concatIndices,\n\t\t\ti.MasterTimeoutSeconds, i.IgnoreUnavailableIndex),\n\t\tmethod: http.MethodDelete,\n\t})\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode != http.StatusOK {\n\t\t\t\treturn responseError.prefixMessage(\"failed to delete indices: \" + concatIndices)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"failed to delete indices: %w\", err)\n\t}\n\treturn nil\n}\n\n// DeleteIndices deletes specified set of indices.\nfunc (i *IndicesClient) DeleteIndices(indices []Index) error {\n\tconcatIndices := \"\"\n\tfor j, index := range indices {\n\t\t// verify the length of the concatIndices\n\t\t// An HTTP line is should not be larger than 4096 bytes\n\t\t// a line contains other than concatIndices data in the request, ie: master_timeout\n\t\t// for a safer side check the line length should not exceed 4000\n\t\tif (len(concatIndices) + len(index.Index)) > 4000 {\n\t\t\terr := i.indexDeleteRequest(concatIndices)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconcatIndices = \"\"\n\t\t}\n\n\t\tconcatIndices += index.Index\n\t\tconcatIndices += \",\"\n\n\t\t// if it is last index, delete request should be executed\n\t\tif j == len(indices)-1 {\n\t\t\treturn i.indexDeleteRequest(concatIndices)\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateIndex an ES index\nfunc (i *IndicesClient) CreateIndex(index string) error {\n\t_, err := i.request(elasticRequest{\n\t\tendpoint: index,\n\t\tmethod:   http.MethodPut,\n\t})\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode != http.StatusOK {\n\t\t\t\treturn responseError.prefixMessage(\"failed to create index: \" + index)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"failed to create index: %w\", err)\n\t}\n\treturn nil\n}\n\n// CreateAlias an ES specific set of index aliases\nfunc (i *IndicesClient) CreateAlias(aliases []Alias) error {\n\terr := i.aliasAction(\"add\", aliases)\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode != http.StatusOK {\n\t\t\t\treturn responseError.prefixMessage(\"failed to create aliases: \" + i.aliasesString(aliases))\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"failed to create aliases: %w\", err)\n\t}\n\treturn nil\n}\n\n// DeleteAlias an ES specific set of index aliases\nfunc (i *IndicesClient) DeleteAlias(aliases []Alias) error {\n\terr := i.aliasAction(\"remove\", aliases)\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode != http.StatusOK {\n\t\t\t\treturn responseError.prefixMessage(\"failed to delete aliases: \" + i.aliasesString(aliases))\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"failed to delete aliases: %w\", err)\n\t}\n\treturn nil\n}\n\n// AliasExists check whether an alias exists or not\nfunc (i *IndicesClient) AliasExists(alias string) (bool, error) {\n\t_, err := i.request(elasticRequest{\n\t\tendpoint: \"_alias/\" + alias,\n\t\tmethod:   http.MethodHead,\n\t})\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode == http.StatusNotFound {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn false, fmt.Errorf(\"failed to check if alias exists: %w\", err)\n\t}\n\treturn true, nil\n}\n\n// IndexExists check whether an index exists or not\nfunc (i *IndicesClient) IndexExists(index string) (bool, error) {\n\t_, err := i.request(elasticRequest{\n\t\tendpoint: index,\n\t\tmethod:   http.MethodHead,\n\t})\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode == http.StatusNotFound {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\treturn false, fmt.Errorf(\"failed to check if index exists: %w\", err)\n\t}\n\treturn true, nil\n}\n\nfunc (*IndicesClient) aliasesString(aliases []Alias) string {\n\tvar builder strings.Builder\n\tfor _, alias := range aliases {\n\t\tfmt.Fprintf(&builder, \"[index: %s, alias: %s],\", alias.Index, alias.Name)\n\t}\n\tconcatAliases := builder.String()\n\treturn strings.Trim(concatAliases, \",\")\n}\n\nfunc (i *IndicesClient) aliasAction(action string, aliases []Alias) error {\n\tactions := []map[string]any{}\n\n\tfor _, alias := range aliases {\n\t\toptions := map[string]any{\n\t\t\t\"index\": alias.Index,\n\t\t\t\"alias\": alias.Name,\n\t\t}\n\t\tif alias.IsWriteIndex {\n\t\t\toptions[\"is_write_index\"] = true\n\t\t}\n\t\tactions = append(actions, map[string]any{\n\t\t\taction: options,\n\t\t})\n\t}\n\n\tbody := map[string]any{\n\t\t\"actions\": actions,\n\t}\n\n\tbodyBytes, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = i.request(elasticRequest{\n\t\tendpoint: \"_aliases\",\n\t\tmethod:   http.MethodPost,\n\t\tbody:     bodyBytes,\n\t})\n\n\treturn err\n}\n\nfunc (i IndicesClient) version() (uint, error) {\n\tcl := ClusterClient{Client: i.Client}\n\treturn cl.Version()\n}\n\n// CreateTemplate an ES index template\nfunc (i IndicesClient) CreateTemplate(template, name string) error {\n\tendpointFmt := \"_template/%s\"\n\tif v, err := i.version(); err != nil {\n\t\treturn err\n\t} else if v >= 8 {\n\t\tendpointFmt = \"_index_template/%s\"\n\t}\n\t_, err := i.request(elasticRequest{\n\t\tendpoint: fmt.Sprintf(endpointFmt, name),\n\t\tmethod:   http.MethodPut,\n\t\tbody:     []byte(template),\n\t})\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode != http.StatusOK {\n\t\t\t\treturn responseError.prefixMessage(\"failed to create template: \" + name)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"failed to create template: %w\", err)\n\t}\n\treturn nil\n}\n\n// Rollover create a rollover for certain index/alias\nfunc (i IndicesClient) Rollover(rolloverTarget string, conditions map[string]any) error {\n\tesReq := elasticRequest{\n\t\tendpoint: rolloverTarget + \"/_rollover/\",\n\t\tmethod:   http.MethodPost,\n\t}\n\tif len(conditions) > 0 {\n\t\tbody := map[string]any{\n\t\t\t\"conditions\": conditions,\n\t\t}\n\t\tbodyBytes, err := json.Marshal(body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tesReq.body = bodyBytes\n\t}\n\t_, err := i.request(esReq)\n\tif err != nil {\n\t\tvar responseError ResponseError\n\t\tif errors.As(err, &responseError) {\n\t\t\tif responseError.StatusCode != http.StatusOK {\n\t\t\t\treturn responseError.prefixMessage(\"failed to create rollover target: \" + rolloverTarget)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"failed to create rollover: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/index_client_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst esIndexResponse = `\n{\n  \"%sjaeger-service-2021-08-06\" : {\n    \"aliases\" : { },\n    \"settings\" : {\n      \"index.creation_date\" : \"1628259381266\",\n      \"index.mapper.dynamic\" : \"false\",\n      \"index.mapping.nested_fields.limit\" : \"50\",\n      \"index.number_of_replicas\" : \"1\",\n      \"index.number_of_shards\" : \"5\",\n      \"index.provided_name\" : \"jaeger-service-2021-08-06\",\n      \"index.requests.cache.enable\" : \"true\",\n      \"index.uuid\" : \"2kKdvrvAT7qXetRzmWhjYQ\",\n      \"index.version.created\" : \"5061099\"\n    }\n  },\n  \"%sjaeger-span-2021-08-06\" : {\n    \"aliases\" : { },\n    \"settings\" : {\n      \"index.creation_date\" : \"1628259381326\",\n      \"index.mapper.dynamic\" : \"false\",\n      \"index.mapping.nested_fields.limit\" : \"50\",\n      \"index.number_of_replicas\" : \"1\",\n      \"index.number_of_shards\" : \"5\",\n      \"index.provided_name\" : \"jaeger-span-2021-08-06\",\n      \"index.requests.cache.enable\" : \"true\",\n      \"index.uuid\" : \"zySRY_FfRFa5YMWxNsNViA\",\n      \"index.version.created\" : \"5061099\"\n    }\n  },\n \"%sjaeger-span-000001\" : {\n    \"aliases\" : {\n      \"jaeger-span-read\" : { },\n      \"jaeger-span-write\" : { }\n    },\n    \"settings\" : {\n      \"index.creation_date\" : \"1628259381326\"\n    }\n  }\n}`\n\nconst esErrResponse = `{\"error\":{\"root_cause\":[{\"type\":\"illegal_argument_exception\",\"reason\":\"request [/jaeger-*] contains unrecognized parameter: [help]\"}],\"type\":\"illegal_argument_exception\",\"reason\":\"request [/jaeger-*] contains unrecognized parameter: [help]\"},\"status\":400}`\n\nfunc TestClientGetIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tprefix       string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t\tindices      []Index\n\t}{\n\t\t{\n\t\t\tname:         \"no error\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     esIndexResponse,\n\t\t\tindices: []Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        \"jaeger-service-2021-08-06\",\n\t\t\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*1628259381266),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        \"jaeger-span-000001\",\n\t\t\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*1628259381326),\n\t\t\t\t\tAliases:      map[string]bool{\"jaeger-span-read\": true, \"jaeger-span-write\": true},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        \"jaeger-span-2021-08-06\",\n\t\t\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*1628259381326),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"no error with prefix\",\n\t\t\tprefix:       \"foo-\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     esIndexResponse,\n\t\t\tindices: []Index{\n\t\t\t\t{\n\t\t\t\t\tIndex:        \"foo-jaeger-service-2021-08-06\",\n\t\t\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*1628259381266),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        \"foo-jaeger-span-000001\",\n\t\t\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*1628259381326),\n\t\t\t\t\tAliases:      map[string]bool{\"jaeger-span-read\": true, \"jaeger-span-write\": true},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndex:        \"foo-jaeger-span-2021-08-06\",\n\t\t\t\t\tCreationTime: time.Unix(0, int64(time.Millisecond)*1628259381326),\n\t\t\t\t\tAliases:      map[string]bool{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to query indices: request failed, status code: 400\",\n\t\t},\n\t\t{\n\t\t\tname:         \"unmarshall error\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     \"AAA\",\n\t\t\terrContains:  `failed to query indices and unmarshall response body: \"AAA\"`,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {\n\t\t\t\tres.WriteHeader(test.responseCode)\n\n\t\t\t\tresponse := test.response\n\t\t\t\tif test.errContains == \"\" {\n\t\t\t\t\t// Formatted string only applies to \"success\" response bodies.\n\t\t\t\t\tresponse = fmt.Sprintf(test.response, test.prefix, test.prefix, test.prefix)\n\t\t\t\t}\n\t\t\t\tres.Write([]byte(response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:   testServer.Client(),\n\t\t\t\t\tEndpoint: testServer.URL,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tindices, err := c.GetJaegerIndices(test.prefix)\n\t\t\tif test.errContains != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.errContains)\n\t\t\t\tassert.Nil(t, indices)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tsort.Slice(indices, func(i, j int) bool {\n\t\t\t\t\treturn indices[i].Index < indices[j].Index\n\t\t\t\t})\n\t\t\t\tassert.Equal(t, test.indices, indices)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc getIndicesList(size int) []Index {\n\tindicesList := []Index{}\n\tfor count := 1; count <= size/2; count++ {\n\t\tindicesList = append(indicesList,\n\t\t\tIndex{Index: fmt.Sprintf(\"jaeger-span-%06d\", count)},\n\t\t\tIndex{Index: fmt.Sprintf(\"jaeger-service-%06d\", count)},\n\t\t)\n\t}\n\treturn indicesList\n}\n\nfunc TestClientDeleteIndices(t *testing.T) {\n\tmasterTimeoutSeconds := 1\n\tmaxURLPathLength := 4000\n\tignoreUnavailableIndex := true\n\n\ttests := []struct {\n\t\tname         string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t\tindices      []Index\n\t\ttriggerAPI   bool\n\t}{\n\t\t{\n\t\t\tname:         \"no indices\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tindices:      []Index{},\n\t\t\ttriggerAPI:   false,\n\t\t},\n\t\t{\n\t\t\tname:         \"one index\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tindices:      []Index{{Index: \"jaeger-span-000001\"}},\n\t\t\ttriggerAPI:   true,\n\t\t},\n\t\t{\n\t\t\tname:         \"moderate indices\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     \"\",\n\t\t\tindices:      getIndicesList(20),\n\t\t\ttriggerAPI:   true,\n\t\t},\n\t\t{\n\t\t\tname:         \"long indices\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\tresponse:     \"\",\n\t\t\tindices:      getIndicesList(600),\n\t\t\ttriggerAPI:   true,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to delete indices: jaeger-span-000001\",\n\t\t\tindices:      []Index{{Index: \"jaeger-span-000001\"}},\n\t\t\ttriggerAPI:   true,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error in long indices\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to delete indices: jaeger-span-000001\",\n\t\t\tindices:      getIndicesList(600),\n\t\t\ttriggerAPI:   true,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tdeletedIndicesCount := 0\n\t\t\tapiTriggered := false\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tapiTriggered = true\n\t\t\t\tassert.Equal(t, http.MethodDelete, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tassert.Equal(t, fmt.Sprintf(\"%ds\", masterTimeoutSeconds), req.URL.Query().Get(\"master_timeout\"))\n\t\t\t\tassert.Equal(t, strconv.FormatBool(ignoreUnavailableIndex), req.URL.Query().Get(\"ignore_unavailable\"))\n\t\t\t\tassert.LessOrEqual(t, len(req.URL.Path), maxURLPathLength)\n\n\t\t\t\t// removes leading '/' and trailing ','\n\t\t\t\t// example: /jaeger-span-000001,  =>  jaeger-span-000001\n\t\t\t\trawIndices := strings.TrimPrefix(req.URL.Path, \"/\")\n\t\t\t\trawIndices = strings.TrimSuffix(rawIndices, \",\")\n\n\t\t\t\tif len(test.indices) == 1 {\n\t\t\t\t\tassert.Equal(t, test.indices[0].Index, rawIndices)\n\t\t\t\t}\n\n\t\t\t\tdeletedIndices := strings.Split(rawIndices, \",\")\n\t\t\t\tdeletedIndicesCount += len(deletedIndices)\n\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t\tMasterTimeoutSeconds:   masterTimeoutSeconds,\n\t\t\t\tIgnoreUnavailableIndex: ignoreUnavailableIndex,\n\t\t\t}\n\n\t\t\terr := c.DeleteIndices(test.indices)\n\t\t\tassert.Equal(t, test.triggerAPI, apiTriggered)\n\n\t\t\tif test.errContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, test.errContains)\n\t\t\t} else {\n\t\t\t\tassert.Len(t, test.indices, deletedIndicesCount)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIndexExists(t *testing.T) {\n\tt.Run(\"index exists\", func(t *testing.T) {\n\t\ttestIndexOrAliasExistence(t, \"index\")\n\t})\n}\n\nfunc TestAliasExists(t *testing.T) {\n\tt.Run(\"alias exists\", func(t *testing.T) {\n\t\ttestIndexOrAliasExistence(t, \"alias\")\n\t})\n}\n\nfunc testIndexOrAliasExistence(t *testing.T, existence string) {\n\tmaxURLPathLength := 4000\n\ttype indexOrAliasExistence struct {\n\t\tname         string\n\t\texists       bool\n\t\tresponseCode int\n\t\texpectedErr  string\n\t}\n\ttests := []indexOrAliasExistence{\n\t\t{\n\t\t\tname:         \"exists\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t\texists:       true,\n\t\t},\n\t\t{\n\t\t\tname:         \"not exists\",\n\t\t\tresponseCode: http.StatusNotFound,\n\t\t\texists:       false,\n\t\t},\n\t}\n\tswitch existence {\n\tcase \"index\":\n\t\ttest := indexOrAliasExistence{\n\t\t\tname:         \"generic error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\texpectedErr:  \"failed to check if index exists: request failed, status code: 400\",\n\t\t}\n\t\ttests = append(tests, test)\n\tcase \"alias\":\n\t\ttest := indexOrAliasExistence{\n\t\t\tname:         \"generic error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\texpectedErr:  \"failed to check if alias exists: request failed, status code: 400\",\n\t\t}\n\t\ttests = append(tests, test)\n\tdefault:\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tapiTriggered := false\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tapiTriggered = true\n\t\t\t\tassert.Equal(t, http.MethodHead, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tassert.LessOrEqual(t, len(req.URL.Path), maxURLPathLength)\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tvar exists bool\n\t\t\tvar err error\n\t\t\tswitch existence {\n\t\t\tcase \"index\":\n\t\t\t\texists, err = c.IndexExists(\"jaeger-span\")\n\t\t\tcase \"alias\":\n\t\t\t\texists, err = c.AliasExists(\"jaeger-span\")\n\t\t\tdefault:\n\t\t\t}\n\t\t\tif test.expectedErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.True(t, apiTriggered)\n\t\t\tassert.Equal(t, test.exists, exists)\n\t\t})\n\t}\n}\n\nfunc TestClientRequestError(t *testing.T) {\n\tc := &IndicesClient{\n\t\tClient: Client{\n\t\t\tEndpoint: \"%\",\n\t\t},\n\t}\n\tindices, err := c.GetJaegerIndices(\"\")\n\trequire.Error(t, err)\n\tassert.Nil(t, indices)\n}\n\nfunc TestClientDoError(t *testing.T) {\n\tc := &IndicesClient{\n\t\tClient: Client{\n\t\t\tClient:   &http.Client{},\n\t\t\tEndpoint: \"localhost:1\",\n\t\t},\n\t}\n\n\tindices, err := c.GetJaegerIndices(\"\")\n\trequire.Error(t, err)\n\tassert.Nil(t, indices)\n}\n\nfunc TestClientCreateIndex(t *testing.T) {\n\tindexName := \"jaeger-span\"\n\ttests := []struct {\n\t\tname         string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t}{\n\t\t{\n\t\t\tname:         \"success\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to create index: jaeger-span\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"jaeger-span\"))\n\t\t\t\tassert.Equal(t, http.MethodPut, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := c.CreateIndex(indexName)\n\t\t\tif test.errContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, test.errContains)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientCreateAliases(t *testing.T) {\n\taliases := []Alias{\n\t\t{\n\t\t\tIndex: \"jaeger-span\",\n\t\t\tName:  \"jaeger-span-read\",\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span\",\n\t\t\tName:         \"jaeger-span-write\",\n\t\t\tIsWriteIndex: true,\n\t\t},\n\t}\n\texpectedRequestBody := `{\"actions\":[{\"add\":{\"alias\":\"jaeger-span-read\",\"index\":\"jaeger-span\"}},{\"add\":{\"alias\":\"jaeger-span-write\",\"index\":\"jaeger-span\",\"is_write_index\":true}}]}`\n\ttests := []struct {\n\t\tname         string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t}{\n\t\t{\n\t\t\tname:         \"success\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to create aliases: [index: jaeger-span, alias: jaeger-span-read],[index: jaeger-span, alias: jaeger-span-write]\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"_aliases\"))\n\t\t\t\tassert.Equal(t, http.MethodPost, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tbody, err := io.ReadAll(req.Body)\n\t\t\t\tif assert.NoError(t, err) {\n\t\t\t\t\tassert.Equal(t, expectedRequestBody, string(body))\n\t\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\t\tres.Write([]byte(test.response))\n\t\t\t\t}\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := c.CreateAlias(aliases)\n\t\t\tif test.errContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, test.errContains)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientDeleteAliases(t *testing.T) {\n\taliases := []Alias{\n\t\t{\n\t\t\tIndex: \"jaeger-span\",\n\t\t\tName:  \"jaeger-span-read\",\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span\",\n\t\t\tName:         \"jaeger-span-write\",\n\t\t\tIsWriteIndex: true,\n\t\t},\n\t}\n\texpectedRequestBody := `{\"actions\":[{\"remove\":{\"alias\":\"jaeger-span-read\",\"index\":\"jaeger-span\"}},{\"remove\":{\"alias\":\"jaeger-span-write\",\"index\":\"jaeger-span\",\"is_write_index\":true}}]}`\n\ttests := []struct {\n\t\tname         string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t}{\n\t\t{\n\t\t\tname:         \"success\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to delete aliases: [index: jaeger-span, alias: jaeger-span-read],[index: jaeger-span, alias: jaeger-span-write]\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"_aliases\"))\n\t\t\t\tassert.Equal(t, http.MethodPost, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tbody, err := io.ReadAll(req.Body)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, expectedRequestBody, string(body))\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := c.DeleteAlias(aliases)\n\t\t\tif test.errContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, test.errContains)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientCreateTemplate(t *testing.T) {\n\ttemplateName := \"jaeger-template\"\n\ttemplateContent := \"template content\"\n\ttests := []struct {\n\t\tname         string\n\t\tversionResp  string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t}{\n\t\t{\n\t\t\tname:         \"success/v7\",\n\t\t\tversionResp:  elasticsearch7,\n\t\t\tresponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"success/v8\",\n\t\t\tversionResp:  elasticsearch8,\n\t\t\tresponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tversionResp:  elasticsearch7,\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to create template: jaeger-template\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tif req.URL.String() == \"/\" { // ES version check\n\t\t\t\t\tres.WriteHeader(http.StatusOK)\n\t\t\t\t\tres.Write([]byte(test.versionResp))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"_template/jaeger-template\"))\n\t\t\t\tassert.Equal(t, http.MethodPut, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tbody, err := io.ReadAll(req.Body)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, templateContent, string(body))\n\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := c.CreateTemplate(templateContent, templateName)\n\t\t\tif test.errContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, test.errContains)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRollover(t *testing.T) {\n\texpectedRequestBody := \"{\\\"conditions\\\":{\\\"max_age\\\":\\\"2d\\\"}}\"\n\tmapConditions := map[string]any{\n\t\t\"max_age\": \"2d\",\n\t}\n\n\ttests := []struct {\n\t\tname         string\n\t\tresponseCode int\n\t\tresponse     string\n\t\terrContains  string\n\t}{\n\t\t{\n\t\t\tname:         \"success\",\n\t\t\tresponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"client error\",\n\t\t\tresponseCode: http.StatusBadRequest,\n\t\t\tresponse:     esErrResponse,\n\t\t\terrContains:  \"failed to create rollover target: jaeger-span\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.True(t, strings.HasSuffix(req.URL.String(), \"jaeger-span/_rollover/\"))\n\t\t\t\tassert.Equal(t, http.MethodPost, req.Method)\n\t\t\t\tassert.Equal(t, \"Basic foobar\", req.Header.Get(\"Authorization\"))\n\t\t\t\tbody, err := io.ReadAll(req.Body)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, expectedRequestBody, string(body))\n\n\t\t\t\tres.WriteHeader(test.responseCode)\n\t\t\t\tres.Write([]byte(test.response))\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tc := &IndicesClient{\n\t\t\t\tClient: Client{\n\t\t\t\t\tClient:    testServer.Client(),\n\t\t\t\t\tEndpoint:  testServer.URL,\n\t\t\t\t\tBasicAuth: \"foobar\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := c.Rollover(\"jaeger-span\", mapConditions)\n\t\t\tif test.errContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, test.errContains)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/interfaces.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\ntype IndexAPI interface {\n\tGetJaegerIndices(prefix string) ([]Index, error)\n\tIndexExists(index string) (bool, error)\n\tAliasExists(alias string) (bool, error)\n\tDeleteIndices(indices []Index) error\n\tCreateIndex(index string) error\n\tCreateAlias(aliases []Alias) error\n\tDeleteAlias(aliases []Alias) error\n\tCreateTemplate(template, name string) error\n\tRollover(rolloverTarget string, conditions map[string]any) error\n}\n\ntype ClusterAPI interface {\n\tVersion() (uint, error)\n}\n\ntype IndexManagementLifecycleAPI interface {\n\tExists(name string) (bool, error)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewIndexAPI creates a new instance of IndexAPI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIndexAPI(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *IndexAPI {\n\tmock := &IndexAPI{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// IndexAPI is an autogenerated mock type for the IndexAPI type\ntype IndexAPI struct {\n\tmock.Mock\n}\n\ntype IndexAPI_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *IndexAPI) EXPECT() *IndexAPI_Expecter {\n\treturn &IndexAPI_Expecter{mock: &_m.Mock}\n}\n\n// AliasExists provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) AliasExists(alias string) (bool, error) {\n\tret := _mock.Called(alias)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for AliasExists\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string) (bool, error)); ok {\n\t\treturn returnFunc(alias)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string) bool); ok {\n\t\tr0 = returnFunc(alias)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string) error); ok {\n\t\tr1 = returnFunc(alias)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndexAPI_AliasExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AliasExists'\ntype IndexAPI_AliasExists_Call struct {\n\t*mock.Call\n}\n\n// AliasExists is a helper method to define mock.On call\n//   - alias string\nfunc (_e *IndexAPI_Expecter) AliasExists(alias interface{}) *IndexAPI_AliasExists_Call {\n\treturn &IndexAPI_AliasExists_Call{Call: _e.mock.On(\"AliasExists\", alias)}\n}\n\nfunc (_c *IndexAPI_AliasExists_Call) Run(run func(alias string)) *IndexAPI_AliasExists_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_AliasExists_Call) Return(b bool, err error) *IndexAPI_AliasExists_Call {\n\t_c.Call.Return(b, err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_AliasExists_Call) RunAndReturn(run func(alias string) (bool, error)) *IndexAPI_AliasExists_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateAlias provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) CreateAlias(aliases []client.Alias) error {\n\tret := _mock.Called(aliases)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateAlias\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func([]client.Alias) error); ok {\n\t\tr0 = returnFunc(aliases)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// IndexAPI_CreateAlias_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateAlias'\ntype IndexAPI_CreateAlias_Call struct {\n\t*mock.Call\n}\n\n// CreateAlias is a helper method to define mock.On call\n//   - aliases []client.Alias\nfunc (_e *IndexAPI_Expecter) CreateAlias(aliases interface{}) *IndexAPI_CreateAlias_Call {\n\treturn &IndexAPI_CreateAlias_Call{Call: _e.mock.On(\"CreateAlias\", aliases)}\n}\n\nfunc (_c *IndexAPI_CreateAlias_Call) Run(run func(aliases []client.Alias)) *IndexAPI_CreateAlias_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []client.Alias\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].([]client.Alias)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_CreateAlias_Call) Return(err error) *IndexAPI_CreateAlias_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_CreateAlias_Call) RunAndReturn(run func(aliases []client.Alias) error) *IndexAPI_CreateAlias_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateIndex provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) CreateIndex(index string) error {\n\tret := _mock.Called(index)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateIndex\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(string) error); ok {\n\t\tr0 = returnFunc(index)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// IndexAPI_CreateIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateIndex'\ntype IndexAPI_CreateIndex_Call struct {\n\t*mock.Call\n}\n\n// CreateIndex is a helper method to define mock.On call\n//   - index string\nfunc (_e *IndexAPI_Expecter) CreateIndex(index interface{}) *IndexAPI_CreateIndex_Call {\n\treturn &IndexAPI_CreateIndex_Call{Call: _e.mock.On(\"CreateIndex\", index)}\n}\n\nfunc (_c *IndexAPI_CreateIndex_Call) Run(run func(index string)) *IndexAPI_CreateIndex_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_CreateIndex_Call) Return(err error) *IndexAPI_CreateIndex_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_CreateIndex_Call) RunAndReturn(run func(index string) error) *IndexAPI_CreateIndex_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateTemplate provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) CreateTemplate(template string, name string) error {\n\tret := _mock.Called(template, name)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateTemplate\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(string, string) error); ok {\n\t\tr0 = returnFunc(template, name)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// IndexAPI_CreateTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTemplate'\ntype IndexAPI_CreateTemplate_Call struct {\n\t*mock.Call\n}\n\n// CreateTemplate is a helper method to define mock.On call\n//   - template string\n//   - name string\nfunc (_e *IndexAPI_Expecter) CreateTemplate(template interface{}, name interface{}) *IndexAPI_CreateTemplate_Call {\n\treturn &IndexAPI_CreateTemplate_Call{Call: _e.mock.On(\"CreateTemplate\", template, name)}\n}\n\nfunc (_c *IndexAPI_CreateTemplate_Call) Run(run func(template string, name string)) *IndexAPI_CreateTemplate_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\tvar arg1 string\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_CreateTemplate_Call) Return(err error) *IndexAPI_CreateTemplate_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_CreateTemplate_Call) RunAndReturn(run func(template string, name string) error) *IndexAPI_CreateTemplate_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// DeleteAlias provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) DeleteAlias(aliases []client.Alias) error {\n\tret := _mock.Called(aliases)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for DeleteAlias\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func([]client.Alias) error); ok {\n\t\tr0 = returnFunc(aliases)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// IndexAPI_DeleteAlias_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAlias'\ntype IndexAPI_DeleteAlias_Call struct {\n\t*mock.Call\n}\n\n// DeleteAlias is a helper method to define mock.On call\n//   - aliases []client.Alias\nfunc (_e *IndexAPI_Expecter) DeleteAlias(aliases interface{}) *IndexAPI_DeleteAlias_Call {\n\treturn &IndexAPI_DeleteAlias_Call{Call: _e.mock.On(\"DeleteAlias\", aliases)}\n}\n\nfunc (_c *IndexAPI_DeleteAlias_Call) Run(run func(aliases []client.Alias)) *IndexAPI_DeleteAlias_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []client.Alias\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].([]client.Alias)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_DeleteAlias_Call) Return(err error) *IndexAPI_DeleteAlias_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_DeleteAlias_Call) RunAndReturn(run func(aliases []client.Alias) error) *IndexAPI_DeleteAlias_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// DeleteIndices provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) DeleteIndices(indices []client.Index) error {\n\tret := _mock.Called(indices)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for DeleteIndices\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func([]client.Index) error); ok {\n\t\tr0 = returnFunc(indices)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// IndexAPI_DeleteIndices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteIndices'\ntype IndexAPI_DeleteIndices_Call struct {\n\t*mock.Call\n}\n\n// DeleteIndices is a helper method to define mock.On call\n//   - indices []client.Index\nfunc (_e *IndexAPI_Expecter) DeleteIndices(indices interface{}) *IndexAPI_DeleteIndices_Call {\n\treturn &IndexAPI_DeleteIndices_Call{Call: _e.mock.On(\"DeleteIndices\", indices)}\n}\n\nfunc (_c *IndexAPI_DeleteIndices_Call) Run(run func(indices []client.Index)) *IndexAPI_DeleteIndices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []client.Index\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].([]client.Index)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_DeleteIndices_Call) Return(err error) *IndexAPI_DeleteIndices_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_DeleteIndices_Call) RunAndReturn(run func(indices []client.Index) error) *IndexAPI_DeleteIndices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetJaegerIndices provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) GetJaegerIndices(prefix string) ([]client.Index, error) {\n\tret := _mock.Called(prefix)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetJaegerIndices\")\n\t}\n\n\tvar r0 []client.Index\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string) ([]client.Index, error)); ok {\n\t\treturn returnFunc(prefix)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string) []client.Index); ok {\n\t\tr0 = returnFunc(prefix)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]client.Index)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string) error); ok {\n\t\tr1 = returnFunc(prefix)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndexAPI_GetJaegerIndices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJaegerIndices'\ntype IndexAPI_GetJaegerIndices_Call struct {\n\t*mock.Call\n}\n\n// GetJaegerIndices is a helper method to define mock.On call\n//   - prefix string\nfunc (_e *IndexAPI_Expecter) GetJaegerIndices(prefix interface{}) *IndexAPI_GetJaegerIndices_Call {\n\treturn &IndexAPI_GetJaegerIndices_Call{Call: _e.mock.On(\"GetJaegerIndices\", prefix)}\n}\n\nfunc (_c *IndexAPI_GetJaegerIndices_Call) Run(run func(prefix string)) *IndexAPI_GetJaegerIndices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_GetJaegerIndices_Call) Return(indexs []client.Index, err error) *IndexAPI_GetJaegerIndices_Call {\n\t_c.Call.Return(indexs, err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_GetJaegerIndices_Call) RunAndReturn(run func(prefix string) ([]client.Index, error)) *IndexAPI_GetJaegerIndices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// IndexExists provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) IndexExists(index string) (bool, error) {\n\tret := _mock.Called(index)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for IndexExists\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string) (bool, error)); ok {\n\t\treturn returnFunc(index)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string) bool); ok {\n\t\tr0 = returnFunc(index)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string) error); ok {\n\t\tr1 = returnFunc(index)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndexAPI_IndexExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IndexExists'\ntype IndexAPI_IndexExists_Call struct {\n\t*mock.Call\n}\n\n// IndexExists is a helper method to define mock.On call\n//   - index string\nfunc (_e *IndexAPI_Expecter) IndexExists(index interface{}) *IndexAPI_IndexExists_Call {\n\treturn &IndexAPI_IndexExists_Call{Call: _e.mock.On(\"IndexExists\", index)}\n}\n\nfunc (_c *IndexAPI_IndexExists_Call) Run(run func(index string)) *IndexAPI_IndexExists_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_IndexExists_Call) Return(b bool, err error) *IndexAPI_IndexExists_Call {\n\t_c.Call.Return(b, err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_IndexExists_Call) RunAndReturn(run func(index string) (bool, error)) *IndexAPI_IndexExists_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Rollover provides a mock function for the type IndexAPI\nfunc (_mock *IndexAPI) Rollover(rolloverTarget string, conditions map[string]any) error {\n\tret := _mock.Called(rolloverTarget, conditions)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Rollover\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(string, map[string]any) error); ok {\n\t\tr0 = returnFunc(rolloverTarget, conditions)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// IndexAPI_Rollover_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rollover'\ntype IndexAPI_Rollover_Call struct {\n\t*mock.Call\n}\n\n// Rollover is a helper method to define mock.On call\n//   - rolloverTarget string\n//   - conditions map[string]any\nfunc (_e *IndexAPI_Expecter) Rollover(rolloverTarget interface{}, conditions interface{}) *IndexAPI_Rollover_Call {\n\treturn &IndexAPI_Rollover_Call{Call: _e.mock.On(\"Rollover\", rolloverTarget, conditions)}\n}\n\nfunc (_c *IndexAPI_Rollover_Call) Run(run func(rolloverTarget string, conditions map[string]any)) *IndexAPI_Rollover_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\tvar arg1 map[string]any\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(map[string]any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexAPI_Rollover_Call) Return(err error) *IndexAPI_Rollover_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *IndexAPI_Rollover_Call) RunAndReturn(run func(rolloverTarget string, conditions map[string]any) error) *IndexAPI_Rollover_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewClusterAPI creates a new instance of ClusterAPI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewClusterAPI(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ClusterAPI {\n\tmock := &ClusterAPI{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ClusterAPI is an autogenerated mock type for the ClusterAPI type\ntype ClusterAPI struct {\n\tmock.Mock\n}\n\ntype ClusterAPI_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ClusterAPI) EXPECT() *ClusterAPI_Expecter {\n\treturn &ClusterAPI_Expecter{mock: &_m.Mock}\n}\n\n// Version provides a mock function for the type ClusterAPI\nfunc (_mock *ClusterAPI) Version() (uint, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Version\")\n\t}\n\n\tvar r0 uint\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (uint, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() uint); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Get(0).(uint)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// ClusterAPI_Version_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Version'\ntype ClusterAPI_Version_Call struct {\n\t*mock.Call\n}\n\n// Version is a helper method to define mock.On call\nfunc (_e *ClusterAPI_Expecter) Version() *ClusterAPI_Version_Call {\n\treturn &ClusterAPI_Version_Call{Call: _e.mock.On(\"Version\")}\n}\n\nfunc (_c *ClusterAPI_Version_Call) Run(run func()) *ClusterAPI_Version_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ClusterAPI_Version_Call) Return(v uint, err error) *ClusterAPI_Version_Call {\n\t_c.Call.Return(v, err)\n\treturn _c\n}\n\nfunc (_c *ClusterAPI_Version_Call) RunAndReturn(run func() (uint, error)) *ClusterAPI_Version_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewIndexManagementLifecycleAPI creates a new instance of IndexManagementLifecycleAPI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIndexManagementLifecycleAPI(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *IndexManagementLifecycleAPI {\n\tmock := &IndexManagementLifecycleAPI{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// IndexManagementLifecycleAPI is an autogenerated mock type for the IndexManagementLifecycleAPI type\ntype IndexManagementLifecycleAPI struct {\n\tmock.Mock\n}\n\ntype IndexManagementLifecycleAPI_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *IndexManagementLifecycleAPI) EXPECT() *IndexManagementLifecycleAPI_Expecter {\n\treturn &IndexManagementLifecycleAPI_Expecter{mock: &_m.Mock}\n}\n\n// Exists provides a mock function for the type IndexManagementLifecycleAPI\nfunc (_mock *IndexManagementLifecycleAPI) Exists(name string) (bool, error) {\n\tret := _mock.Called(name)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Exists\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string) (bool, error)); ok {\n\t\treturn returnFunc(name)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string) bool); ok {\n\t\tr0 = returnFunc(name)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string) error); ok {\n\t\tr1 = returnFunc(name)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndexManagementLifecycleAPI_Exists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exists'\ntype IndexManagementLifecycleAPI_Exists_Call struct {\n\t*mock.Call\n}\n\n// Exists is a helper method to define mock.On call\n//   - name string\nfunc (_e *IndexManagementLifecycleAPI_Expecter) Exists(name interface{}) *IndexManagementLifecycleAPI_Exists_Call {\n\treturn &IndexManagementLifecycleAPI_Exists_Call{Call: _e.mock.On(\"Exists\", name)}\n}\n\nfunc (_c *IndexManagementLifecycleAPI_Exists_Call) Run(run func(name string)) *IndexManagementLifecycleAPI_Exists_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexManagementLifecycleAPI_Exists_Call) Return(b bool, err error) *IndexManagementLifecycleAPI_Exists_Call {\n\t_c.Call.Return(b, err)\n\treturn _c\n}\n\nfunc (_c *IndexManagementLifecycleAPI_Exists_Call) RunAndReturn(run func(name string) (bool, error)) *IndexManagementLifecycleAPI_Exists_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/client.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/olivere/elastic/v7\"\n)\n\n// Client is an abstraction for elastic.Client\ntype Client interface {\n\tIndexExists(index string) IndicesExistsService\n\tCreateIndex(index string) IndicesCreateService\n\tCreateTemplate(id string) TemplateCreateService\n\tIndex() IndexService\n\tSearch(indices ...string) SearchService\n\tMultiSearch() MultiSearchService\n\tDeleteIndex(index string) IndicesDeleteService\n\tio.Closer\n\tGetVersion() uint\n}\n\n// IndicesExistsService is an abstraction for elastic.IndicesExistsService\ntype IndicesExistsService interface {\n\tDo(ctx context.Context) (bool, error)\n}\n\n// IndicesCreateService is an abstraction for elastic.IndicesCreateService\ntype IndicesCreateService interface {\n\tBody(mapping string) IndicesCreateService\n\tDo(ctx context.Context) (*elastic.IndicesCreateResult, error)\n}\n\n// IndicesDeleteService is an abstraction for elastic.IndicesDeleteService\ntype IndicesDeleteService interface {\n\tDo(ctx context.Context) (*elastic.IndicesDeleteResponse, error)\n}\n\n// TemplateCreateService is an abstraction for creating a mapping\ntype TemplateCreateService interface {\n\tBody(mapping string) TemplateCreateService\n\tDo(ctx context.Context) (*elastic.IndicesPutTemplateResponse, error)\n}\n\n// IndexService is an abstraction for elastic BulkService\ntype IndexService interface {\n\tIndex(index string) IndexService\n\tType(typ string) IndexService\n\tId(id string) IndexService\n\tBodyJson(body any) IndexService\n\tAdd()\n}\n\n// SearchService is an abstraction for elastic.SearchService\ntype SearchService interface {\n\tSize(size int) SearchService\n\tAggregation(name string, aggregation elastic.Aggregation) SearchService\n\tIgnoreUnavailable(ignoreUnavailable bool) SearchService\n\tQuery(query elastic.Query) SearchService\n\tDo(ctx context.Context) (*elastic.SearchResult, error)\n}\n\n// MultiSearchService is an abstraction for elastic.MultiSearchService\ntype MultiSearchService interface {\n\tAdd(requests ...*elastic.SearchRequest) MultiSearchService\n\tIndex(indices ...string) MultiSearchService\n\tDo(ctx context.Context) (*elastic.MultiSearchResult, error)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/config/auth_helper.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth\"\n\t\"github.com/jaegertracing/jaeger/internal/auth/apikey\"\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n)\n\n// initTokenAuthWithTime initializes token authentication injectable time for testing\nfunc initTokenAuthWithTime(tokenAuth *TokenAuthentication, scheme string, logger *zap.Logger, timeFn func() time.Time) (*auth.Method, error) {\n\tif tokenAuth == nil || (tokenAuth.FilePath == \"\" && !tokenAuth.AllowFromContext) {\n\t\treturn nil, nil\n\t}\n\n\tif tokenAuth.FilePath != \"\" && tokenAuth.AllowFromContext {\n\t\tlogger.Warn(\"Both token file and context propagation are enabled - context token will take precedence over file-based token\",\n\t\t\tzap.String(\"auth_scheme\", scheme))\n\t}\n\n\tvar tokenFn func() string\n\tvar fromCtx func(context.Context) (string, bool)\n\n\t// File-based token setup\n\tif tokenAuth.FilePath != \"\" {\n\t\ttf, err := auth.TokenProviderWithTime(tokenAuth.FilePath, tokenAuth.ReloadInterval, logger, timeFn)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttokenFn = tf\n\t}\n\n\t// Context-based token setup\n\tif tokenAuth.AllowFromContext {\n\t\tswitch scheme {\n\t\tcase \"Bearer\":\n\t\t\tfromCtx = bearertoken.GetBearerToken\n\t\tcase \"APIKey\":\n\t\t\tfromCtx = apikey.GetAPIKey\n\t\tdefault:\n\t\t}\n\t}\n\n\treturn &auth.Method{\n\t\tScheme:  scheme,\n\t\tTokenFn: tokenFn,\n\t\tFromCtx: fromCtx,\n\t}, nil\n}\n\n// Simplified init functions - directly call shared implementation\nfunc initBearerAuth(tokenAuth *TokenAuthentication, logger *zap.Logger) (*auth.Method, error) {\n\tif tokenAuth == nil {\n\t\treturn nil, nil\n\t}\n\treturn initTokenAuthWithTime(tokenAuth, \"Bearer\", logger, time.Now)\n}\n\nfunc initAPIKeyAuth(tokenAuth *TokenAuthentication, logger *zap.Logger) (*auth.Method, error) {\n\tif tokenAuth == nil {\n\t\treturn nil, nil\n\t}\n\treturn initTokenAuthWithTime(tokenAuth, \"APIKey\", logger, time.Now)\n}\n\n// Keep initBasicAuth unchanged\nfunc initBasicAuth(basicAuth *BasicAuthentication, logger *zap.Logger) (*auth.Method, error) {\n\treturn initBasicAuthWithTime(basicAuth, logger, time.Now)\n}\n\nfunc initBasicAuthWithTime(basicAuth *BasicAuthentication, logger *zap.Logger, timeFn func() time.Time) (*auth.Method, error) {\n\tif basicAuth == nil {\n\t\treturn nil, nil\n\t}\n\n\tif basicAuth.Password != \"\" && basicAuth.PasswordFilePath != \"\" {\n\t\treturn nil, errors.New(\"both Password and PasswordFilePath are set\")\n\t}\n\n\tusername := basicAuth.Username\n\tif username == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tvar tokenFn func() string\n\t// Handle password from file or static password\n\tif basicAuth.PasswordFilePath != \"\" {\n\t\t// Use TokenProvider for password loading\n\t\tpasswordFn, err := auth.TokenProviderWithTime(basicAuth.PasswordFilePath, basicAuth.ReloadInterval, logger, timeFn)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to load password from file: %w\", err)\n\t\t}\n\n\t\t// Pre-encode credentials in TokenFn\n\t\ttokenFn = func() string {\n\t\t\tpassword := passwordFn()\n\t\t\tif password == \"\" {\n\t\t\t\treturn \"\"\n\t\t\t}\n\t\t\tcredentials := username + \":\" + password\n\t\t\treturn base64.StdEncoding.EncodeToString([]byte(credentials))\n\t\t}\n\t} else {\n\t\t// Static password - pre-encode once\n\t\tpassword := basicAuth.Password\n\t\tcredentials := username + \":\" + password\n\t\tencodedCredentials := base64.StdEncoding.EncodeToString([]byte(credentials))\n\t\ttokenFn = func() string { return encodedCredentials }\n\t}\n\n\treturn &auth.Method{\n\t\tScheme:  \"Basic\",\n\t\tTokenFn: tokenFn, // Returns base64-encoded credentials\n\t}, nil\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/config/auth_helper_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"encoding/base64\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest/observer\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth\"\n)\n\n// TestInitBearerAuth tests bearer token authentication initialization\nfunc TestInitBearerAuth(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttempDir := t.TempDir()\n\tbearerFile := filepath.Join(tempDir, \"bearer-token\")\n\trequire.NoError(t, os.WriteFile(bearerFile, []byte(\"test-bearer\"), 0o600))\n\n\ttests := []struct {\n\t\tname        string\n\t\tbearerAuth  *TokenAuthentication\n\t\texpectError bool\n\t\texpectNil   bool\n\t\tvalidate    func(t *testing.T, method *auth.Method)\n\t}{\n\t\t{\n\t\t\tname: \"Valid file-based bearer auth\",\n\t\t\tbearerAuth: &TokenAuthentication{\n\t\t\t\tFilePath: bearerFile,\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\texpectNil:   false,\n\t\t\tvalidate: func(t *testing.T, method *auth.Method) {\n\t\t\t\trequire.NotNil(t, method)\n\t\t\t\tassert.Equal(t, \"Bearer\", method.Scheme)\n\t\t\t\tassert.NotNil(t, method.TokenFn)\n\t\t\t\tassert.Equal(t, \"test-bearer\", method.TokenFn())\n\t\t\t\tassert.Nil(t, method.FromCtx)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Valid context-based bearer auth\",\n\t\t\tbearerAuth: &TokenAuthentication{\n\t\t\t\tAllowFromContext: true,\n\t\t\t},\n\t\t\texpectError: false,\n\t\t\texpectNil:   false,\n\t\t\tvalidate: func(t *testing.T, method *auth.Method) {\n\t\t\t\trequire.NotNil(t, method)\n\t\t\t\tassert.Equal(t, \"Bearer\", method.Scheme)\n\t\t\t\tassert.Nil(t, method.TokenFn)\n\t\t\t\tassert.NotNil(t, method.FromCtx)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"Nil bearer auth returns nil\",\n\t\t\tbearerAuth: nil,\n\t\t\texpectNil:  true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmethod, err := initBearerAuth(tc.bearerAuth, logger)\n\t\t\tswitch {\n\t\t\tcase tc.expectError:\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Nil(t, method)\n\t\t\tcase tc.expectNil:\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Nil(t, method)\n\t\t\tdefault:\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\ttc.validate(t, method)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestInitAPIKeyAuth tests API key authentication initialization\nfunc TestInitAPIKeyAuth(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttempDir := t.TempDir()\n\tapiKeyFile := filepath.Join(tempDir, \"api-key\")\n\trequire.NoError(t, os.WriteFile(apiKeyFile, []byte(\"test-apikey\"), 0o600))\n\n\ttests := []struct {\n\t\tname        string\n\t\tapiKeyAuth  *TokenAuthentication\n\t\texpectError bool\n\t\texpectNil   bool\n\t\tvalidate    func(t *testing.T, method *auth.Method)\n\t}{\n\t\t{\n\t\t\tname: \"Valid file-based API key auth\",\n\t\t\tapiKeyAuth: &TokenAuthentication{\n\t\t\t\tFilePath: apiKeyFile,\n\t\t\t},\n\t\t\tvalidate: func(t *testing.T, method *auth.Method) {\n\t\t\t\trequire.NotNil(t, method)\n\t\t\t\tassert.Equal(t, \"APIKey\", method.Scheme)\n\t\t\t\tassert.NotNil(t, method.TokenFn)\n\t\t\t\tassert.Equal(t, \"test-apikey\", method.TokenFn())\n\t\t\t\tassert.Nil(t, method.FromCtx)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Valid context-based API key auth\",\n\t\t\tapiKeyAuth: &TokenAuthentication{\n\t\t\t\tAllowFromContext: true,\n\t\t\t},\n\t\t\tvalidate: func(t *testing.T, method *auth.Method) {\n\t\t\t\trequire.NotNil(t, method)\n\t\t\t\tassert.Equal(t, \"APIKey\", method.Scheme)\n\t\t\t\tassert.Nil(t, method.TokenFn)\n\t\t\t\tassert.NotNil(t, method.FromCtx)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"Nil API key auth returns nil\",\n\t\t\tapiKeyAuth: nil,\n\t\t\texpectNil:  true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmethod, err := initAPIKeyAuth(tc.apiKeyAuth, logger)\n\t\t\tswitch {\n\t\t\tcase tc.expectError:\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Nil(t, method)\n\t\t\tcase tc.expectNil:\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Nil(t, method)\n\t\t\tdefault:\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\ttc.validate(t, method)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test multiple auth types working together\nfunc TestMultipleTokenAuth(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttempDir := t.TempDir()\n\n\tbearerFile := filepath.Join(tempDir, \"bearer\")\n\tapiKeyFile := filepath.Join(tempDir, \"apikey\")\n\trequire.NoError(t, os.WriteFile(bearerFile, []byte(\"bearer-token\"), 0o600))\n\trequire.NoError(t, os.WriteFile(apiKeyFile, []byte(\"api-key-token\"), 0o600))\n\n\tbearerAuth := &TokenAuthentication{\n\t\tFilePath: bearerFile,\n\t}\n\tapiKeyAuth := &TokenAuthentication{\n\t\tFilePath: apiKeyFile,\n\t}\n\n\tbearerMethod, err := initBearerAuth(bearerAuth, logger)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, bearerMethod)\n\n\tapiKeyMethod, err := initAPIKeyAuth(apiKeyAuth, logger)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, apiKeyMethod)\n\n\tassert.Equal(t, \"Bearer\", bearerMethod.Scheme)\n\tassert.Equal(t, \"APIKey\", apiKeyMethod.Scheme)\n\tassert.Equal(t, \"bearer-token\", bearerMethod.TokenFn())\n\tassert.Equal(t, \"api-key-token\", apiKeyMethod.TokenFn())\n}\n\n// TestInitBasicAuth tests basic authentication initialization\nfunc TestInitBasicAuth(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttempDir := t.TempDir()\n\tpasswordFile := filepath.Join(tempDir, \"password\")\n\trequire.NoError(t, os.WriteFile(passwordFile, []byte(\"testpass\"), 0o600))\n\n\ttests := []struct {\n\t\tname        string\n\t\tbasicAuth   *BasicAuthentication\n\t\texpectError bool\n\t\texpectNil   bool\n\t\tvalidate    func(t *testing.T, method *auth.Method)\n\t}{\n\t\t{\n\t\t\tname: \"Static password basic auth\",\n\t\t\tbasicAuth: &BasicAuthentication{\n\t\t\t\tUsername: \"user\",\n\t\t\t\tPassword: \"pass\",\n\t\t\t},\n\t\t\texpectNil: false,\n\t\t\tvalidate: func(t *testing.T, method *auth.Method) {\n\t\t\t\tassert.Equal(t, \"Basic\", method.Scheme)\n\t\t\t\tassert.NotNil(t, method.TokenFn)\n\t\t\t\t// Verify base64 encoded \"user:pass\"\n\t\t\t\texpected := base64.StdEncoding.EncodeToString([]byte(\"user:pass\"))\n\t\t\t\tassert.Equal(t, expected, method.TokenFn())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"File-based password basic auth\",\n\t\t\tbasicAuth: &BasicAuthentication{\n\t\t\t\tUsername:         \"user\",\n\t\t\t\tPasswordFilePath: passwordFile,\n\t\t\t},\n\t\t\texpectNil: false,\n\t\t\tvalidate: func(t *testing.T, method *auth.Method) {\n\t\t\t\tassert.Equal(t, \"Basic\", method.Scheme)\n\t\t\t\tassert.NotNil(t, method.TokenFn)\n\t\t\t\t// Verify base64 encoded \"user:testpass\"\n\t\t\t\texpected := base64.StdEncoding.EncodeToString([]byte(\"user:testpass\"))\n\t\t\t\tassert.Equal(t, expected, method.TokenFn())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"No username returns nil\",\n\t\t\tbasicAuth: &BasicAuthentication{\n\t\t\t\tPassword: \"pass\",\n\t\t\t},\n\t\t\texpectNil: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmethod, err := initBasicAuth(tc.basicAuth, logger)\n\t\t\tif tc.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tif tc.expectNil {\n\t\t\t\t\tassert.Nil(t, method)\n\t\t\t\t} else {\n\t\t\t\t\ttc.validate(t, method)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestInitBasicAuthWithReload tests password file reloading\nfunc TestInitBasicAuthWithReload(t *testing.T) {\n\tcurrentTime := time.Unix(0, 0)\n\ttimeFn := func() time.Time { return currentTime }\n\n\ttempDir := t.TempDir()\n\tpasswordFile := filepath.Join(tempDir, \"password\")\n\trequire.NoError(t, os.WriteFile(passwordFile, []byte(\"initial\"), 0o600))\n\n\tlogger := zap.NewNop()\n\tbasicAuth := &BasicAuthentication{\n\t\tUsername:         \"user\",\n\t\tPasswordFilePath: passwordFile,\n\t\tReloadInterval:   50 * time.Millisecond,\n\t}\n\n\tmethod, err := initBasicAuthWithTime(basicAuth, logger, timeFn)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, method)\n\n\t// Initial token\n\tinitialExpected := base64.StdEncoding.EncodeToString([]byte(\"user:initial\"))\n\tassert.Equal(t, initialExpected, method.TokenFn())\n\n\t// Update password file\n\trequire.NoError(t, os.WriteFile(passwordFile, []byte(\"updated\"), 0o600))\n\n\t// Before reload interval - should return cached\n\tcurrentTime = currentTime.Add(25 * time.Millisecond)\n\tassert.Equal(t, initialExpected, method.TokenFn())\n\n\t// After reload interval - should return updated\n\tcurrentTime = currentTime.Add(50 * time.Millisecond)\n\tupdatedExpected := base64.StdEncoding.EncodeToString([]byte(\"user:updated\"))\n\tassert.Equal(t, updatedExpected, method.TokenFn())\n}\n\nfunc TestInitBasicAuth_EdgeCases(t *testing.T) {\n\tlogger := zap.NewNop()\n\n\ttests := []struct {\n\t\tname        string\n\t\tbasicAuth   *BasicAuthentication\n\t\texpectError bool\n\t\texpectNil   bool\n\t\terrorMsg    string\n\t}{\n\t\t{\n\t\t\tname:      \"nil basicAuth returns nil\",\n\t\t\tbasicAuth: nil,\n\t\t\texpectNil: true,\n\t\t},\n\t\t{\n\t\t\tname: \"both password and file path set - validation error\",\n\t\t\tbasicAuth: &BasicAuthentication{\n\t\t\t\tUsername:         \"user\",\n\t\t\t\tPassword:         \"pass\",\n\t\t\t\tPasswordFilePath: \"/some/path\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"both Password and PasswordFilePath are set\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty username returns nil\",\n\t\t\tbasicAuth: &BasicAuthentication{\n\t\t\t\tUsername: \"\",\n\t\t\t\tPassword: \"pass\",\n\t\t\t},\n\t\t\texpectNil: true,\n\t\t},\n\t\t{\n\t\t\tname: \"file path error\",\n\t\t\tbasicAuth: &BasicAuthentication{\n\t\t\t\tUsername:         \"user\",\n\t\t\t\tPasswordFilePath: \"/nonexistent/path\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"failed to load password from file\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmethod, err := initBasicAuth(tc.basicAuth, logger)\n\t\t\tswitch {\n\t\t\tcase tc.expectError:\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tc.errorMsg)\n\t\t\t\tassert.Nil(t, method)\n\t\t\tcase tc.expectNil:\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Nil(t, method)\n\t\t\tdefault:\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, method)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test warning logs for conflicting configuration\nfunc TestTokenAuth_WarningLogs(t *testing.T) {\n\tcore, logs := observer.New(zap.WarnLevel)\n\tlogger := zap.New(core)\n\ttempDir := t.TempDir()\n\n\ttokenFile := filepath.Join(tempDir, \"token\")\n\trequire.NoError(t, os.WriteFile(tokenFile, []byte(\"test-token\"), 0o600))\n\n\tbase := &TokenAuthentication{\n\t\tFilePath:         tokenFile,\n\t\tAllowFromContext: true,\n\t}\n\n\tmethod, err := initTokenAuthWithTime(base, \"Bearer\", logger, time.Now)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, method)\n\n\t// Check that warning was logged\n\trequire.Equal(t, 1, logs.Len())\n\tlogEntry := logs.All()[0]\n\tassert.Equal(t, zap.WarnLevel, logEntry.Level)\n\tassert.Contains(t, logEntry.Message, \"Both token file and context propagation are enabled\")\n\tassert.Equal(t, \"Bearer\", logEntry.Context[0].String)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/config/config.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"maps\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/asaskevich/govalidator\"\n\tesv8 \"github.com/elastic/go-elasticsearch/v9\"\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.opentelemetry.io/collector/config/configauth\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"go.uber.org/zap/zapgrpc\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\teswrapper \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/wrapper\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/spanstoremetrics\"\n)\n\nconst (\n\tIndexPrefixSeparator = \"-\"\n)\n\n// IndexOptions describes the index format and rollover frequency\ntype IndexOptions struct {\n\t// Priority contains the priority of index template (ESv8 only).\n\tPriority int64 `mapstructure:\"priority\"`\n\t// DateLayout contains the format string used to format current time to part of the index name.\n\t// For example, \"2006-01-02\" layout will result in \"jaeger-spans-yyyy-mm-dd\".\n\t// If not specified, the default value is \"2006-01-02\".\n\t// See https://pkg.go.dev/time#Layout for more details on the syntax.\n\tDateLayout string `mapstructure:\"date_layout\"`\n\t// Shards is the number of shards per index in Elasticsearch.\n\tShards int64 `mapstructure:\"shards\"`\n\t// Replicas is the number of replicas per index in Elasticsearch.\n\tReplicas *int64 `mapstructure:\"replicas\"`\n\t// RolloverFrequency contains the rollover frequency setting used to fetch\n\t// indices from elasticsearch.\n\t// Valid configuration options are: [hour, day].\n\t// This setting does not affect the index rotation and is simply used for\n\t// fetching indices.\n\tRolloverFrequency string `mapstructure:\"rollover_frequency\"`\n}\n\n// Indices describes different configuration options for each index type\ntype Indices struct {\n\t// IndexPrefix is an optional prefix to prepend to Jaeger indices.\n\t// For example, setting this field to \"production\" creates \"production-jaeger-*\".\n\tIndexPrefix  IndexPrefix  `mapstructure:\"index_prefix\"`\n\tSpans        IndexOptions `mapstructure:\"spans\"`\n\tServices     IndexOptions `mapstructure:\"services\"`\n\tDependencies IndexOptions `mapstructure:\"dependencies\"`\n\tSampling     IndexOptions `mapstructure:\"sampling\"`\n}\n\ntype bulkCallback struct {\n\tstartTimes sync.Map\n\tsm         *spanstoremetrics.WriteMetrics\n\tlogger     *zap.Logger\n}\n\ntype IndexPrefix string\n\nfunc (p IndexPrefix) Apply(indexName string) string {\n\tps := string(p)\n\tif ps == \"\" {\n\t\treturn indexName\n\t}\n\tif strings.HasSuffix(ps, IndexPrefixSeparator) {\n\t\treturn ps + indexName\n\t}\n\treturn ps + IndexPrefixSeparator + indexName\n}\n\n// Configuration describes the configuration properties needed to connect to an ElasticSearch cluster\ntype Configuration struct {\n\t// ---- connection related configs ----\n\t// Servers is a list of Elasticsearch servers. The strings must must contain full URLs\n\t// (i.e. http://localhost:9200).\n\tServers []string `mapstructure:\"server_urls\" valid:\"required,url\"`\n\t// RemoteReadClusters is a list of Elasticsearch remote cluster names for cross-cluster\n\t// querying.\n\tRemoteReadClusters []string       `mapstructure:\"remote_read_clusters\"`\n\tAuthentication     Authentication `mapstructure:\"auth\"`\n\t// TLS contains the TLS configuration for the connection to the ElasticSearch clusters.\n\tTLS      configtls.ClientConfig `mapstructure:\"tls\"`\n\tSniffing Sniffing               `mapstructure:\"sniffing\"`\n\t// Disable the Elasticsearch health check\n\tDisableHealthCheck bool `mapstructure:\"disable_health_check\"`\n\t// Set the Elasticsearch health check timeout startup\n\tHealthCheckTimeoutStartup time.Duration `mapstructure:\"health_check_timeout_startup\"`\n\t// SendGetBodyAs is the HTTP verb to use for requests that contain a body.\n\tSendGetBodyAs string `mapstructure:\"send_get_body_as\"`\n\t// QueryTimeout contains the timeout used for queries. A timeout of zero means no timeout.\n\tQueryTimeout time.Duration `mapstructure:\"query_timeout\"`\n\t// HTTPCompression can be set to false to disable gzip compression for requests to ElasticSearch\n\tHTTPCompression bool `mapstructure:\"http_compression\"`\n\n\t// CustomHeaders contains custom HTTP headers to be sent with every request to Elasticsearch.\n\t// This is useful for scenarios like AWS SigV4 proxy authentication where specific headers\n\t// (like Host) need to be set for proper request signing.\n\tCustomHeaders map[string]string `mapstructure:\"custom_headers\"`\n\t// ---- elasticsearch client related configs ----\n\tBulkProcessing BulkProcessing `mapstructure:\"bulk_processing\"`\n\t// Version contains the major Elasticsearch version. If this field is not specified,\n\t// the value will be auto-detected from Elasticsearch.\n\tVersion uint `mapstructure:\"version\"`\n\t// LogLevel contains the Elasticsearch client log-level. Valid values for this field\n\t// are: [debug, info, error]\n\tLogLevel string `mapstructure:\"log_level\"`\n\n\t// ---- index related configs ----\n\tIndices Indices `mapstructure:\"indices\"`\n\t// UseReadWriteAliases, if set to true, will use read and write aliases for indices.\n\t// Use this option with Elasticsearch rollover API. It requires an external component\n\t// to create aliases before startup and then performing its management.\n\tUseReadWriteAliases bool `mapstructure:\"use_aliases\"`\n\t// SpanReadAlias specifies the exact alias name to use for reading spans.\n\t// When set, Jaeger will use this alias directly without any modifications.\n\t// This allows integration with existing Elasticsearch setups that have custom alias names.\n\t// Can only be used with UseReadWriteAliases=true.\n\t// Example: \"my-custom-span-reader\"\n\tSpanReadAlias string `mapstructure:\"span_read_alias\"`\n\t// SpanWriteAlias specifies the exact alias name to use for writing spans.\n\t// When set, Jaeger will use this alias directly without any modifications.\n\t// Can only be used with UseReadWriteAliases=true.\n\t// Example: \"my-custom-span-writer\"\n\tSpanWriteAlias string `mapstructure:\"span_write_alias\"`\n\t// ServiceReadAlias specifies the exact alias name to use for reading services.\n\t// When set, Jaeger will use this alias directly without any modifications.\n\t// Can only be used with UseReadWriteAliases=true.\n\t// Example: \"my-custom-service-reader\"\n\tServiceReadAlias string `mapstructure:\"service_read_alias\"`\n\t// ServiceWriteAlias specifies the exact alias name to use for writing services.\n\t// When set, Jaeger will use this alias directly without any modifications.\n\t// Can only be used with UseReadWriteAliases=true.\n\t// Example: \"my-custom-service-writer\"\n\tServiceWriteAlias string `mapstructure:\"service_write_alias\"`\n\t// ReadAliasSuffix is the suffix to append to the index name used for reading.\n\t// This configuration only exists to provide backwards compatibility for jaeger-v1\n\t// which is why it is not exposed as a configuration option for jaeger-v2\n\tReadAliasSuffix string `mapstructure:\"-\"`\n\t// WriteAliasSuffix is the suffix to append to the write index name.\n\t// This configuration only exists to provide backwards compatibility for jaeger-v1\n\t// which is why it is not exposed as a configuration option for jaeger-v2\n\tWriteAliasSuffix string `mapstructure:\"-\"`\n\t// CreateIndexTemplates, if set to true, creates index templates at application startup.\n\t// This configuration should be set to false when templates are installed manually.\n\tCreateIndexTemplates bool `mapstructure:\"create_mappings\"`\n\t// Option to enable Index Lifecycle Management (ILM) for Jaeger span and service indices.\n\t// Read more about ILM at\n\t// https://www.jaegertracing.io/docs/deployment/#enabling-ilm-support\n\tUseILM bool `mapstructure:\"use_ilm\"`\n\n\t// ---- jaeger-specific configs ----\n\t// MaxDocCount Defines maximum number of results to fetch from storage per query.\n\tMaxDocCount int `mapstructure:\"max_doc_count\"`\n\t// MaxSpanAge configures the maximum lookback on span reads.\n\tMaxSpanAge time.Duration `mapstructure:\"max_span_age\"`\n\t// ServiceCacheTTL contains the TTL for the cache of known service names.\n\tServiceCacheTTL time.Duration `mapstructure:\"service_cache_ttl\"`\n\t// AdaptiveSamplingLookback contains the duration to look back for the\n\t// latest adaptive sampling probabilities.\n\tAdaptiveSamplingLookback time.Duration `mapstructure:\"adaptive_sampling_lookback\"`\n\tTags                     TagsAsFields  `mapstructure:\"tags_as_fields\"`\n\t// Enabled, if set to true, enables the namespace for storage pointed to by this configuration.\n\tEnabled bool `mapstructure:\"-\"`\n}\n\n// TagsAsFields holds configuration for tag schema.\n// By default Jaeger stores tags in an array of nested objects.\n// This configurations allows to store tags as object fields for better Kibana support.\ntype TagsAsFields struct {\n\t// Store all tags as object fields, instead nested objects\n\tAllAsFields bool `mapstructure:\"all\"`\n\t// Dot replacement for tag keys when stored as object fields\n\tDotReplacement string `mapstructure:\"dot_replacement\"`\n\t// File path to tag keys which should be stored as object fields\n\tFile string `mapstructure:\"config_file\"`\n\t// Comma delimited list of tags to store as object fields\n\tInclude string `mapstructure:\"include\"`\n}\n\n// Sniffing sets the sniffing configuration for the ElasticSearch client, which is the process\n// of finding all the nodes of your cluster. Read more about sniffing at\n// https://github.com/olivere/elastic/wiki/Sniffing.\ntype Sniffing struct {\n\t// Enabled, if set to true, enables sniffing for the ElasticSearch client.\n\tEnabled bool `mapstructure:\"enabled\"`\n\t// UseHTTPS, if set to true, sets the HTTP scheme to HTTPS when performing sniffing.\n\t// For ESV8, the scheme is set to HTTPS by default, so this configuration is ignored.\n\tUseHTTPS bool `mapstructure:\"use_https\"`\n}\n\ntype BulkProcessing struct {\n\t// MaxBytes, contains the number of bytes which specifies when to flush.\n\tMaxBytes int `mapstructure:\"max_bytes\"`\n\t// MaxActions contain the number of added actions which specifies when to flush.\n\tMaxActions int `mapstructure:\"max_actions\"`\n\t// FlushInterval is the interval at the end of which a flush occurs.\n\tFlushInterval time.Duration `mapstructure:\"flush_interval\"`\n\t// Workers contains the number of concurrent workers allowed to be executed.\n\tWorkers int `mapstructure:\"workers\"`\n}\n\n// TokenAuthentication contains the common fields shared by all token-based authentication methods\ntype TokenAuthentication struct {\n\t// FilePath contains the path to a file containing the token.\n\tFilePath string `mapstructure:\"file_path\"`\n\t// AllowFromContext, if set to true, allows the token to be retrieved from the context.\n\tAllowFromContext bool `mapstructure:\"from_context\"`\n\t// ReloadInterval contains the interval at which the token file is reloaded.\n\t// If set to 0 then the file is only loaded once on startup.\n\tReloadInterval time.Duration `mapstructure:\"reload_interval\"`\n}\n\ntype Authentication struct {\n\tBasicAuthentication configoptional.Optional[BasicAuthentication] `mapstructure:\"basic\"`\n\tBearerTokenAuth     configoptional.Optional[TokenAuthentication] `mapstructure:\"bearer_token\"`\n\tAPIKeyAuth          configoptional.Optional[TokenAuthentication] `mapstructure:\"api_key\"`\n\tconfigauth.Config   `mapstructure:\",squash\"`\n}\n\ntype BasicAuthentication struct {\n\t// Username contains the username required to connect to Elasticsearch.\n\tUsername string `mapstructure:\"username\"`\n\t// Password contains The password required by Elasticsearch\n\tPassword string `mapstructure:\"password\" json:\"-\"`\n\t// PasswordFilePath contains the path to a file containing password.\n\t// This file is watched for changes.\n\tPasswordFilePath string `mapstructure:\"password_file\"`\n\t// ReloadInterval contains the interval at which the password file is reloaded.\n\t// If set to 0 then the file is only loaded once on startup.\n\tReloadInterval time.Duration `mapstructure:\"reload_interval\"`\n}\n\n// BearerTokenAuthentication contains the configuration for attaching bearer tokens\n// when making HTTP requests. Note that TokenFilePath and AllowTokenFromContext\n// should not both be enabled. If both TokenFilePath and AllowTokenFromContext are set,\n// the TokenFilePath will be ignored.\n// For more information about token-based authentication in elasticsearch, check out\n// https://www.elastic.co/guide/en/elasticsearch/reference/current/token-authentication-services.html.\n\n// NewClient creates a new ElasticSearch client\nfunc NewClient(ctx context.Context, c *Configuration, logger *zap.Logger, metricsFactory metrics.Factory, httpAuth extensionauth.HTTPClient) (es.Client, error) {\n\tif len(c.Servers) < 1 {\n\t\treturn nil, errors.New(\"no servers specified\")\n\t}\n\toptions, err := c.getConfigOptions(ctx, logger, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trawClient, err := elastic.NewClient(options...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbcb := bulkCallback{\n\t\tsm:     spanstoremetrics.NewWriter(metricsFactory, \"bulk_index\"),\n\t\tlogger: logger,\n\t}\n\n\tif c.Version == 0 {\n\t\t// Determine ElasticSearch Version\n\t\tpingResult, pingStatus, err := rawClient.Ping(c.Servers[0]).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Non-2xx responses aren't reported as errors by the ping code (7.0.32 version of\n\t\t// the elastic client).\n\t\tif pingStatus < 200 || pingStatus >= 300 {\n\t\t\treturn nil, fmt.Errorf(\"ElasticSearch server %s returned HTTP %d, expected 2xx\", c.Servers[0], pingStatus)\n\t\t}\n\n\t\t// The deserialization in the ping implementation may succeed even if the response\n\t\t// contains no relevant properties and we may get empty values in that case.\n\t\tif pingResult.Version.Number == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"ElasticSearch server %s returned invalid ping response\", c.Servers[0])\n\t\t}\n\n\t\tesVersion, err := strconv.Atoi(string(pingResult.Version.Number[0]))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// OpenSearch is based on ES 7.x\n\t\tif strings.Contains(pingResult.TagLine, \"OpenSearch\") {\n\t\t\tif pingResult.Version.Number[0] == '1' {\n\t\t\t\tlogger.Info(\"OpenSearch 1.x detected, using ES 7.x index mappings\")\n\t\t\t\tesVersion = 7\n\t\t\t}\n\t\t\tif pingResult.Version.Number[0] == '2' {\n\t\t\t\tlogger.Info(\"OpenSearch 2.x detected, using ES 7.x index mappings\")\n\t\t\t\tesVersion = 7\n\t\t\t}\n\t\t\tif pingResult.Version.Number[0] == '3' {\n\t\t\t\tlogger.Info(\"OpenSearch 3.x detected, using ES 7.x index mappings\")\n\t\t\t\tesVersion = 7\n\t\t\t}\n\t\t}\n\t\tlogger.Info(\"Elasticsearch detected\", zap.Int(\"version\", esVersion))\n\t\tc.Version = uint(esVersion)\n\t}\n\n\tvar rawClientV8 *esv8.Client\n\tif c.Version >= 8 {\n\t\trawClientV8, err = newElasticsearchV8(ctx, c, logger, httpAuth)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error creating v8 client: %w\", err)\n\t\t}\n\t}\n\n\tbulkProc, err := rawClient.BulkProcessor().\n\t\tBefore(func(id int64, _ /* requests */ []elastic.BulkableRequest) {\n\t\t\tbcb.startTimes.Store(id, time.Now())\n\t\t}).\n\t\tAfter(bcb.invoke).\n\t\tBulkSize(c.BulkProcessing.MaxBytes).\n\t\tWorkers(c.BulkProcessing.Workers).\n\t\tBulkActions(c.BulkProcessing.MaxActions).\n\t\tFlushInterval(c.BulkProcessing.FlushInterval).\n\t\tDo(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn eswrapper.WrapESClient(rawClient, bulkProc, c.Version, rawClientV8), nil\n}\n\nfunc (bcb *bulkCallback) invoke(id int64, requests []elastic.BulkableRequest, response *elastic.BulkResponse, err error) {\n\tstart, ok := bcb.startTimes.Load(id)\n\tif ok {\n\t\tbcb.startTimes.Delete(id)\n\t} else {\n\t\tstart = time.Now()\n\t}\n\n\t// Log individual errors\n\tif response != nil && response.Errors {\n\t\tfor _, it := range response.Items {\n\t\t\tfor key, val := range it {\n\t\t\t\tif val.Error != nil {\n\t\t\t\t\tbcb.logger.Error(\"Elasticsearch part of bulk request failed\",\n\t\t\t\t\t\tzap.String(\"map-key\", key), zap.Reflect(\"response\", val))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlatency := time.Since(start.(time.Time))\n\tif err != nil {\n\t\tbcb.sm.LatencyErr.Record(latency)\n\t} else {\n\t\tbcb.sm.LatencyOk.Record(latency)\n\t}\n\n\tvar failed int\n\tif response != nil {\n\t\tfailed = len(response.Failed())\n\t}\n\n\ttotal := len(requests)\n\tbcb.sm.Attempts.Inc(int64(total))\n\tbcb.sm.Inserts.Inc(int64(total - failed))\n\tbcb.sm.Errors.Inc(int64(failed))\n\n\tif err != nil {\n\t\tbcb.logger.Error(\"Elasticsearch could not process bulk request\",\n\t\t\tzap.Int(\"request_count\", total),\n\t\t\tzap.Int(\"failed_count\", failed),\n\t\t\tzap.Error(err),\n\t\t\tzap.Any(\"response\", response))\n\t}\n}\n\nfunc newElasticsearchV8(ctx context.Context, c *Configuration, logger *zap.Logger, httpAuth extensionauth.HTTPClient) (*esv8.Client, error) {\n\tvar options esv8.Config\n\toptions.Addresses = c.Servers\n\tif c.Authentication.BasicAuthentication.HasValue() {\n\t\tbasicAuth := c.Authentication.BasicAuthentication.Get()\n\t\toptions.Username = basicAuth.Username\n\t\toptions.Password = basicAuth.Password\n\t}\n\toptions.DiscoverNodesOnStart = c.Sniffing.Enabled\n\toptions.CompressRequestBody = c.HTTPCompression\n\n\tif len(c.CustomHeaders) > 0 {\n\t\theaders := make(http.Header)\n\t\tfor key, value := range c.CustomHeaders {\n\t\t\theaders.Set(key, value)\n\t\t}\n\t\toptions.Header = headers\n\t}\n\n\ttransport, err := GetHTTPRoundTripper(ctx, c, logger, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toptions.Transport = transport\n\treturn esv8.NewClient(options)\n}\n\nfunc setDefaultIndexOptions(target, source *IndexOptions) {\n\tif target.Shards == 0 {\n\t\ttarget.Shards = source.Shards\n\t}\n\n\tif target.Replicas == nil {\n\t\ttarget.Replicas = source.Replicas\n\t}\n\n\tif target.Priority == 0 {\n\t\ttarget.Priority = source.Priority\n\t}\n\n\tif target.DateLayout == \"\" {\n\t\ttarget.DateLayout = source.DateLayout\n\t}\n\n\tif target.RolloverFrequency == \"\" {\n\t\ttarget.RolloverFrequency = source.RolloverFrequency\n\t}\n}\n\n// ApplyDefaults copies settings from source unless its own value is non-zero.\nfunc (c *Configuration) ApplyDefaults(source *Configuration) {\n\tif len(c.RemoteReadClusters) == 0 {\n\t\tc.RemoteReadClusters = source.RemoteReadClusters\n\t}\n\t// Handle BasicAuthentication defaults\n\tsourceHasBasicAuth := source.Authentication.BasicAuthentication.HasValue()\n\ttargetHasBasicAuth := c.Authentication.BasicAuthentication.HasValue()\n\tif sourceHasBasicAuth {\n\t\t// If target doesn't have BasicAuth, copy it from source\n\t\tif !targetHasBasicAuth {\n\t\t\tc.Authentication.BasicAuthentication = source.Authentication.BasicAuthentication\n\t\t} else {\n\t\t\t// Target has BasicAuth, apply field-level defaults\n\t\t\tsourceBasicAuth := source.Authentication.BasicAuthentication.Get()\n\t\t\t// Make a copy of target BasicAuth\n\t\t\tbasicAuth := *c.Authentication.BasicAuthentication.Get()\n\n\t\t\t// Apply defaults for username if not set\n\t\t\tif basicAuth.Username == \"\" && sourceBasicAuth.Username != \"\" {\n\t\t\t\tbasicAuth.Username = sourceBasicAuth.Username\n\t\t\t}\n\t\t\t// Apply defaults for password if not set\n\t\t\tif basicAuth.Password == \"\" && sourceBasicAuth.Password != \"\" {\n\t\t\t\tbasicAuth.Password = sourceBasicAuth.Password\n\t\t\t}\n\n\t\t\t// Only update BasicAuthentication if we have values to set\n\t\t\tif basicAuth.Username != \"\" || basicAuth.Password != \"\" {\n\t\t\t\tc.Authentication.BasicAuthentication = configoptional.Some(basicAuth)\n\t\t\t}\n\t\t}\n\t}\n\tif !c.Sniffing.Enabled {\n\t\tc.Sniffing.Enabled = source.Sniffing.Enabled\n\t}\n\tif c.MaxSpanAge == 0 {\n\t\tc.MaxSpanAge = source.MaxSpanAge\n\t}\n\tif c.AdaptiveSamplingLookback == 0 {\n\t\tc.AdaptiveSamplingLookback = source.AdaptiveSamplingLookback\n\t}\n\tif c.Indices.IndexPrefix == \"\" {\n\t\tc.Indices.IndexPrefix = source.Indices.IndexPrefix\n\t}\n\n\tsetDefaultIndexOptions(&c.Indices.Spans, &source.Indices.Spans)\n\tsetDefaultIndexOptions(&c.Indices.Services, &source.Indices.Services)\n\tsetDefaultIndexOptions(&c.Indices.Dependencies, &source.Indices.Dependencies)\n\n\tif c.BulkProcessing.MaxBytes == 0 {\n\t\tc.BulkProcessing.MaxBytes = source.BulkProcessing.MaxBytes\n\t}\n\tif c.BulkProcessing.Workers == 0 {\n\t\tc.BulkProcessing.Workers = source.BulkProcessing.Workers\n\t}\n\tif c.BulkProcessing.MaxActions == 0 {\n\t\tc.BulkProcessing.MaxActions = source.BulkProcessing.MaxActions\n\t}\n\tif c.BulkProcessing.FlushInterval == 0 {\n\t\tc.BulkProcessing.FlushInterval = source.BulkProcessing.FlushInterval\n\t}\n\tif !c.Sniffing.UseHTTPS {\n\t\tc.Sniffing.UseHTTPS = source.Sniffing.UseHTTPS\n\t}\n\tif !c.Tags.AllAsFields {\n\t\tc.Tags.AllAsFields = source.Tags.AllAsFields\n\t}\n\tif c.Tags.DotReplacement == \"\" {\n\t\tc.Tags.DotReplacement = source.Tags.DotReplacement\n\t}\n\tif c.Tags.Include == \"\" {\n\t\tc.Tags.Include = source.Tags.Include\n\t}\n\tif c.Tags.File == \"\" {\n\t\tc.Tags.File = source.Tags.File\n\t}\n\tif c.MaxDocCount == 0 {\n\t\tc.MaxDocCount = source.MaxDocCount\n\t}\n\tif c.LogLevel == \"\" {\n\t\tc.LogLevel = source.LogLevel\n\t}\n\tif c.SendGetBodyAs == \"\" {\n\t\tc.SendGetBodyAs = source.SendGetBodyAs\n\t}\n\tif !c.HTTPCompression {\n\t\tc.HTTPCompression = source.HTTPCompression\n\t}\n\tif c.CustomHeaders == nil && len(source.CustomHeaders) > 0 {\n\t\tc.CustomHeaders = make(map[string]string)\n\t\tmaps.Copy(c.CustomHeaders, source.CustomHeaders)\n\t}\n}\n\n// RolloverFrequencyAsNegativeDuration returns the index rollover frequency duration for the given frequency string\nfunc RolloverFrequencyAsNegativeDuration(frequency string) time.Duration {\n\tif frequency == \"hour\" {\n\t\treturn -1 * time.Hour\n\t}\n\treturn -24 * time.Hour\n}\n\n// TagKeysAsFields returns tags from the file and command line merged\nfunc (c *Configuration) TagKeysAsFields() ([]string, error) {\n\tvar tags []string\n\n\t// from file\n\tif c.Tags.File != \"\" {\n\t\tfile, err := os.Open(filepath.Clean(c.Tags.File))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tscanner := bufio.NewScanner(file)\n\t\tfor scanner.Scan() {\n\t\t\tline := scanner.Text()\n\t\t\tif tag := strings.TrimSpace(line); tag != \"\" {\n\t\t\t\ttags = append(tags, tag)\n\t\t\t}\n\t\t}\n\t\tif err := file.Close(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// from params\n\tif c.Tags.Include != \"\" {\n\t\ttags = append(tags, strings.Split(c.Tags.Include, \",\")...)\n\t}\n\n\treturn tags, nil\n}\n\nfunc (c *Configuration) getESOptions(disableHealthCheck bool) []elastic.ClientOptionFunc {\n\t// Get base Elasticsearch options\n\toptions := []elastic.ClientOptionFunc{\n\t\telastic.SetURL(c.Servers...), elastic.SetSniff(c.Sniffing.Enabled), elastic.SetHealthcheck(!disableHealthCheck),\n\t}\n\tif c.HealthCheckTimeoutStartup > 0 {\n\t\toptions = append(options, elastic.SetHealthcheckTimeoutStartup(c.HealthCheckTimeoutStartup))\n\t}\n\tif c.Sniffing.UseHTTPS {\n\t\toptions = append(options, elastic.SetScheme(\"https\"))\n\t}\n\tif c.SendGetBodyAs != \"\" {\n\t\toptions = append(options, elastic.SetSendGetBodyAs(c.SendGetBodyAs))\n\t}\n\toptions = append(options, elastic.SetGzip(c.HTTPCompression))\n\treturn options\n}\n\n// getConfigOptions wraps the configs to feed to the ElasticSearch client init\nfunc (c *Configuration) getConfigOptions(ctx context.Context, logger *zap.Logger, httpAuth extensionauth.HTTPClient) ([]elastic.ClientOptionFunc, error) {\n\t// (has problems on AWS OpenSearch) see https://github.com/jaegertracing/jaeger/pull/7212\n\t// Disable health check only in the following cases:\n\t// 1. When health check is explicitly disabled\n\t// 2. When tokens are EXCLUSIVELY available from context (not from file)\n\t//    because at startup we don't have a valid token to do the health check\n\tdisableHealthCheck := c.DisableHealthCheck\n\n\t// Check if we have bearer token or API key authentication that only allows from context\n\tif c.Authentication.BearerTokenAuth.HasValue() || c.Authentication.APIKeyAuth.HasValue() {\n\t\tbearerAuth := c.Authentication.BearerTokenAuth.Get()\n\t\tapiKeyAuth := c.Authentication.APIKeyAuth.Get()\n\n\t\tdisableHealthCheck = disableHealthCheck ||\n\t\t\t(bearerAuth != nil && bearerAuth.AllowFromContext && bearerAuth.FilePath == \"\") ||\n\t\t\t(apiKeyAuth != nil && apiKeyAuth.AllowFromContext && apiKeyAuth.FilePath == \"\")\n\t}\n\n\t// Get base Elasticsearch options using the helper function\n\toptions := c.getESOptions(disableHealthCheck)\n\t// Configure HTTP transport with TLS and authentication\n\ttransport, err := GetHTTPRoundTripper(ctx, c, logger, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// HTTP client setup with timeout and transport\n\thttpClient := &http.Client{\n\t\tTimeout:   c.QueryTimeout,\n\t\tTransport: transport,\n\t}\n\n\toptions = append(options, elastic.SetHttpClient(httpClient))\n\n\t// Add logging configuration\n\toptions, err = addLoggerOptions(options, c.LogLevel, logger)\n\tif err != nil {\n\t\treturn options, err\n\t}\n\n\treturn options, nil\n}\n\nfunc addLoggerOptions(options []elastic.ClientOptionFunc, logLevel string, logger *zap.Logger) ([]elastic.ClientOptionFunc, error) {\n\t// Decouple ES logger from the log-level assigned to the parent application's log-level; otherwise, the least\n\t// permissive log-level will dominate.\n\t// e.g. --log-level=info and --es.log-level=debug would mute ES's debug logging and would require --log-level=debug\n\t// to show ES debug logs.\n\tvar lvl zapcore.Level\n\tvar setLogger func(logger elastic.Logger) elastic.ClientOptionFunc\n\n\tswitch logLevel {\n\tcase \"debug\":\n\t\tlvl = zap.DebugLevel\n\t\tsetLogger = elastic.SetTraceLog\n\tcase \"info\":\n\t\tlvl = zap.InfoLevel\n\t\tsetLogger = elastic.SetInfoLog\n\tcase \"error\":\n\t\tlvl = zap.ErrorLevel\n\t\tsetLogger = elastic.SetErrorLog\n\tdefault:\n\t\treturn options, fmt.Errorf(\"unrecognized log-level: \\\"%s\\\"\", logLevel)\n\t}\n\n\tesLogger := logger.WithOptions(\n\t\tzap.IncreaseLevel(lvl),\n\t\tzap.AddCallerSkip(2), // to ensure the right caller:lineno are logged\n\t)\n\n\t// Elastic client requires a \"Printf\"-able logger.\n\tl := zapgrpc.NewLogger(esLogger)\n\toptions = append(options, setLogger(l))\n\treturn options, nil\n}\n\n// GetHTTPRoundTripper returns configured http.RoundTripper with optional HTTP authenticator.\n// Pass nil for httpAuth if authentication is not required.\nfunc GetHTTPRoundTripper(ctx context.Context, c *Configuration, logger *zap.Logger, httpAuth extensionauth.HTTPClient) (http.RoundTripper, error) {\n\t// Configure base transport.\n\ttransport := &http.Transport{\n\t\tProxy: http.ProxyFromEnvironment,\n\t}\n\n\t// Configure TLS.\n\tif c.TLS.Insecure {\n\t\t// #nosec G402\n\t\ttransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}\n\t} else {\n\t\ttlsConfig, err := c.TLS.LoadTLSConfig(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttransport.TLSClientConfig = tlsConfig\n\t}\n\n\t// Initialize authentication methods.\n\tvar authMethods []auth.Method\n\t// API Key Authentication\n\tif c.Authentication.APIKeyAuth.HasValue() {\n\t\tapiKeyAuth := c.Authentication.APIKeyAuth.Get()\n\t\tak, err := initAPIKeyAuth(apiKeyAuth, logger)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to initialize API key authentication: %w\", err)\n\t\t}\n\t\tif ak != nil {\n\t\t\tauthMethods = append(authMethods, *ak)\n\t\t}\n\t}\n\n\t// Bearer Token Authentication\n\tif c.Authentication.BearerTokenAuth.HasValue() {\n\t\tbearerAuth := c.Authentication.BearerTokenAuth.Get()\n\t\tba, err := initBearerAuth(bearerAuth, logger)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to initialize bearer authentication: %w\", err)\n\t\t}\n\t\tif ba != nil {\n\t\t\tauthMethods = append(authMethods, *ba)\n\t\t}\n\t}\n\n\t// Basic Authentication\n\tif c.Authentication.BasicAuthentication.HasValue() {\n\t\tbasicAuth := c.Authentication.BasicAuthentication.Get()\n\t\tba, err := initBasicAuth(basicAuth, logger)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to initialize basic authentication: %w\", err)\n\t\t}\n\t\tif ba != nil {\n\t\t\tauthMethods = append(authMethods, *ba)\n\t\t}\n\t}\n\n\t// Wrap with authentication layer.\n\tvar roundTripper http.RoundTripper = transport\n\tif len(authMethods) > 0 {\n\t\troundTripper = &auth.RoundTripper{\n\t\t\tTransport: transport,\n\t\t\tAuths:     authMethods,\n\t\t}\n\t}\n\n\t// Apply HTTP authenticator extension if configured (e.g., SigV4)\n\tif httpAuth != nil {\n\t\twrappedRT, err := httpAuth.RoundTripper(roundTripper)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to wrap round tripper with HTTP authenticator: %w\", err)\n\t\t}\n\t\treturn wrappedRT, nil\n\t}\n\n\treturn roundTripper, nil\n}\n\nfunc (c *Configuration) Validate() error {\n\t_, err := govalidator.ValidateStruct(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.UseILM && !c.UseReadWriteAliases {\n\t\treturn errors.New(\"UseILM must always be used in conjunction with UseReadWriteAliases to ensure ES writers and readers refer to the single index mapping\")\n\t}\n\tif c.CreateIndexTemplates && c.UseILM {\n\t\treturn errors.New(\"when UseILM is set true, CreateIndexTemplates must be set to false and index templates must be created by init process of es-rollover app\")\n\t}\n\n\t// Validate explicit alias settings require UseReadWriteAliases\n\thasAnyExplicitAlias := c.SpanReadAlias != \"\" || c.SpanWriteAlias != \"\" ||\n\t\tc.ServiceReadAlias != \"\" || c.ServiceWriteAlias != \"\"\n\n\tif hasAnyExplicitAlias && !c.UseReadWriteAliases {\n\t\treturn errors.New(\"explicit aliases (span_read_alias, span_write_alias, service_read_alias, service_write_alias) require UseReadWriteAliases to be true\")\n\t}\n\n\t// Validate that if any alias is set, all four should be set (for consistency)\n\thasSpanAliases := c.SpanReadAlias != \"\" || c.SpanWriteAlias != \"\"\n\thasServiceAliases := c.ServiceReadAlias != \"\" || c.ServiceWriteAlias != \"\"\n\n\tif hasSpanAliases && (c.SpanReadAlias == \"\" || c.SpanWriteAlias == \"\") {\n\t\treturn errors.New(\"both span_read_alias and span_write_alias must be set together\")\n\t}\n\n\tif hasServiceAliases && (c.ServiceReadAlias == \"\" || c.ServiceWriteAlias == \"\") {\n\t\treturn errors.New(\"both service_read_alias and service_write_alias must be set together\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/config/config_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth\"\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/spanstoremetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nvar mockEsServerResponseWithVersion0 = []byte(`\n{\n\t\"Version\": {\n\t\t\"Number\": \"0\"\n\t}\n}\n`)\n\nvar mockEsServerResponseWithVersion1 = []byte(`\n{\n\t\"tagline\": \"OpenSearch\",\n\t\"Version\": {\n\t\t\"Number\": \"1\"\n\t}\n}\n`)\n\nvar mockEsServerResponseWithVersion2 = []byte(`\n{\n\t\"tagline\": \"OpenSearch\",\n\t\"Version\": {\n\t\t\"Number\": \"2\"\n\t}\n}\n`)\n\nvar mockEsServerResponseWithVersion3 = []byte(`\n{\n\t\"tagline\": \"OpenSearch\",\n\t\"Version\": {\n\t\t\"Number\": \"3\"\n\t}\n}\n`)\n\nvar mockEsServerResponseWithVersion8 = []byte(`\n{\n\t\"tagline\": \"OpenSearch\",\n\t\"Version\": {\n\t\t\"Number\": \"9\"\n\t}\n}\n`)\n\nfunc copyToTempFile(t *testing.T, pattern string, filename string) (file *os.File) {\n\ttempDir := t.TempDir()\n\ttempFilePath := tempDir + \"/\" + pattern\n\ttempFile, err := os.Create(tempFilePath)\n\trequire.NoError(t, err)\n\tdata, err := os.ReadFile(filename)\n\trequire.NoError(t, err)\n\t_, err = tempFile.Write(data)\n\trequire.NoError(t, err)\n\trequire.NoError(t, tempFile.Close())\n\treturn tempFile\n}\n\n// basicAuth creates basic authentication component\nfunc basicAuth(username, password, passwordFilePath string) configoptional.Optional[BasicAuthentication] {\n\treturn configoptional.Some(BasicAuthentication{\n\t\tUsername:         username,\n\t\tPassword:         password,\n\t\tPasswordFilePath: passwordFilePath,\n\t})\n}\n\n// bearerAuth creates bearer token authentication component\nfunc bearerAuth(filePath string, allowFromContext bool) configoptional.Optional[TokenAuthentication] {\n\treturn configoptional.Some(TokenAuthentication{\n\t\tFilePath:         filePath,\n\t\tAllowFromContext: allowFromContext,\n\t})\n}\n\n// apiKeyAuth creates api key authentication component\nfunc apiKeyAuth(filePath string, allowFromContext bool) configoptional.Optional[TokenAuthentication] {\n\treturn configoptional.Some(TokenAuthentication{\n\t\tFilePath:         filePath,\n\t\tAllowFromContext: allowFromContext,\n\t})\n}\n\nfunc TestNewClient(t *testing.T) {\n\tconst (\n\t\tpwd1       = \"password\"\n\t\ttoken      = \"token\"\n\t\tserverCert = \"../../../../internal/config/tlscfg/testdata/example-server-cert.pem\"\n\t\tapiKey     = \"test-api-key\"\n\t)\n\tapiKeyFile := filepath.Join(t.TempDir(), \"api-key\")\n\trequire.NoError(t, os.WriteFile(apiKeyFile, []byte(apiKey), 0o600))\n\n\tpwdFile := filepath.Join(t.TempDir(), \"pwd\")\n\trequire.NoError(t, os.WriteFile(pwdFile, []byte(pwd1), 0o600))\n\tpwdtokenFile := filepath.Join(t.TempDir(), \"token\")\n\trequire.NoError(t, os.WriteFile(pwdtokenFile, []byte(token), 0o600))\n\t// copy certs to temp so we can modify them\n\tcertFilePath := copyToTempFile(t, \"cert.crt\", serverCert)\n\tdefer certFilePath.Close()\n\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t// Accept both GET and HEAD requests\n\t\tassert.Contains(t, []string{http.MethodGet, http.MethodHead}, req.Method)\n\t\tres.WriteHeader(http.StatusOK)\n\t\tres.Write(mockEsServerResponseWithVersion0)\n\t}))\n\tdefer testServer.Close()\n\n\ttestServer1 := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tassert.Contains(t, []string{http.MethodGet, http.MethodHead}, req.Method)\n\t\tres.WriteHeader(http.StatusOK)\n\t\tres.Write(mockEsServerResponseWithVersion1)\n\t}))\n\tdefer testServer1.Close()\n\n\ttestServer2 := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tassert.Contains(t, []string{http.MethodGet, http.MethodHead}, req.Method)\n\t\tres.WriteHeader(http.StatusOK)\n\t\tres.Write(mockEsServerResponseWithVersion2)\n\t}))\n\tdefer testServer2.Close()\n\n\ttestServer3 := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tassert.Contains(t, []string{http.MethodGet, http.MethodHead}, req.Method)\n\t\tres.WriteHeader(http.StatusOK)\n\t\tres.Write(mockEsServerResponseWithVersion3)\n\t}))\n\tdefer testServer3.Close()\n\n\ttestServer8 := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tassert.Contains(t, []string{http.MethodGet, http.MethodHead}, req.Method)\n\t\tres.WriteHeader(http.StatusOK)\n\t\tres.Write(mockEsServerResponseWithVersion8)\n\t}))\n\tdefer testServer8.Close()\n\n\ttests := []struct {\n\t\tname          string\n\t\tconfig        *Configuration\n\t\texpectedError bool\n\t}{\n\t\t{\n\t\t\tname: \"success with valid configuration\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t\tVersion: 8,\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration and tls enabled\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t\tVersion: 0,\n\t\t\t\tTLS:     configtls.ClientConfig{Insecure: false},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration and reading token and certificate from file\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(pwdtokenFile, true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t\tVersion: 0,\n\t\t\t\tTLS: configtls.ClientConfig{\n\t\t\t\t\tInsecure: true,\n\t\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\t\tCAFile: certFilePath.Name(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with invalid configuration of version higher than 8\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer8.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t\tVersion: 9,\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration with version 1\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer1.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration with version 2\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer2.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration with version 3\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer3.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration password from file\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"\", pwdFile),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"fail with configuration password and password from file are set\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", pwdFile),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"fail with missing server\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"fail with invalid configuration invalid loglevel\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"invalid\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"fail with invalid configuration invalid bulkworkers number\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"invalid\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tWorkers:  0,\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration and info log level\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tWorkers:  0,\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with valid configuration and error log level\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t\tBearerTokenAuth:     bearerAuth(\"\", true),\n\t\t\t\t\tAPIKeyAuth:          apiKeyAuth(\"\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"error\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1, // disable bulk; we want immediate flush\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with API key from file\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tAPIKeyAuth: apiKeyAuth(apiKeyFile, false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1,\n\t\t\t\t},\n\t\t\t\tVersion: 8,\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with API key from context only\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tAPIKeyAuth: apiKeyAuth(\"\", true),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1,\n\t\t\t\t},\n\t\t\t\tVersion: 8,\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"success with API key from both file and context\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tAPIKeyAuth: apiKeyAuth(apiKeyFile, true),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1,\n\t\t\t\t},\n\t\t\t\tVersion: 8,\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"fail with invalid API key file path\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{testServer.URL},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tAPIKeyAuth: apiKeyAuth(\"/nonexistent/api-key\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1,\n\t\t\t\t},\n\t\t\t\tVersion: 8,\n\t\t\t},\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"success with API key context-only disables health check\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:            []string{testServer.URL},\n\t\t\t\tDisableHealthCheck: false,\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tAPIKeyAuth: apiKeyAuth(\"\", true),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"debug\",\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes: -1,\n\t\t\t\t},\n\t\t\t\tVersion: 8,\n\t\t\t},\n\t\t\texpectedError: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tlogger := zap.NewNop()\n\t\t\tmetricsFactory := metrics.NullFactory\n\t\t\tconfig := test.config\n\t\t\tclient, err := NewClient(context.Background(), config, logger, metricsFactory, nil)\n\t\t\tif test.expectedError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Nil(t, client)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, client)\n\t\t\t\terr = client.Close()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewClientPingErrorHandling(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tserverResponse []byte\n\t\tstatusCode     int\n\t\texpectedError  string\n\t}{\n\t\t{\n\t\t\tname:           \"ping returns 404 status\",\n\t\t\tserverResponse: mockEsServerResponseWithVersion0,\n\t\t\tstatusCode:     404,\n\t\t\texpectedError:  \"ElasticSearch server\",\n\t\t},\n\t\t{\n\t\t\tname:           \"ping returns 500 status\",\n\t\t\tserverResponse: mockEsServerResponseWithVersion0,\n\t\t\tstatusCode:     500,\n\t\t\texpectedError:  \"ElasticSearch server\",\n\t\t},\n\t\t{\n\t\t\tname:           \"ping returns 300 status\",\n\t\t\tserverResponse: mockEsServerResponseWithVersion0,\n\t\t\tstatusCode:     300,\n\t\t\texpectedError:  \"ElasticSearch server\",\n\t\t},\n\t\t{\n\t\t\tname:           \"ping returns empty version number\",\n\t\t\tserverResponse: []byte(`{\"Version\": {\"Number\": \"\"}}`),\n\t\t\tstatusCode:     200,\n\t\t\texpectedError:  \"invalid ping response\",\n\t\t},\n\n\t\t{\n\t\t\tname:           \"ping returns valid 200 status with version\",\n\t\t\tserverResponse: mockEsServerResponseWithVersion0,\n\t\t\tstatusCode:     200,\n\t\t\texpectedError:  \"\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\tassert.Contains(t, []string{http.MethodGet, http.MethodHead}, req.Method)\n\t\t\t\tres.WriteHeader(test.statusCode)\n\t\t\t\tres.Write(test.serverResponse)\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tconfig := &Configuration{\n\t\t\t\tServers:            []string{testServer.URL},\n\t\t\t\tLogLevel:           \"error\",\n\t\t\t\tDisableHealthCheck: true,\n\t\t\t}\n\n\t\t\tlogger := zap.NewNop()\n\t\t\tmetricsFactory := metrics.NullFactory\n\t\t\tclient, err := NewClient(context.Background(), config, logger, metricsFactory, nil)\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), test.expectedError)\n\t\t\t\trequire.Nil(t, client)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, client)\n\t\t\t\terr = client.Close()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewClientVersionDetection(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tserverResponse  []byte\n\t\texpectedVersion uint\n\t\texpectedError   string\n\t}{\n\t\t{\n\t\t\tname: \"version number with letters\",\n\t\t\tserverResponse: []byte(`{\n                \"Version\": {\n                    \"Number\": \"7.x.1\"\n                }\n            }`),\n\t\t\texpectedVersion: 7,\n\t\t\texpectedError:   \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty version number should fail validation\",\n\t\t\tserverResponse: []byte(`{\n                \"Version\": {\n                    \"Number\": \"\"\n                }\n            }`),\n\t\t\texpectedError: \"invalid ping response\",\n\t\t},\n\t\t{\n\t\t\tname: \"version number as numeric should fail JSON parsing\",\n\t\t\tserverResponse: []byte(`{\n                \"Version\": {\n                    \"Number\": 7\n                }\n            }`),\n\t\t\texpectedError: \"cannot unmarshal number\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) {\n\t\t\t\tres.WriteHeader(http.StatusOK)\n\t\t\t\tres.Write(test.serverResponse)\n\t\t\t}))\n\t\t\tdefer testServer.Close()\n\n\t\t\tconfig := &Configuration{\n\t\t\t\tServers:            []string{testServer.URL},\n\t\t\t\tLogLevel:           \"error\",\n\t\t\t\tDisableHealthCheck: true,\n\t\t\t}\n\n\t\t\tlogger := zap.NewNop()\n\t\t\tmetricsFactory := metrics.NullFactory\n\t\t\tclient, err := NewClient(context.Background(), config, logger, metricsFactory, nil)\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), test.expectedError)\n\t\t\t\trequire.Nil(t, client)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, client)\n\t\t\t\tassert.Equal(t, test.expectedVersion, config.Version)\n\t\t\t\terr = client.Close()\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestApplyDefaults(t *testing.T) {\n\tsource := &Configuration{\n\t\tRemoteReadClusters: []string{\"cluster1\", \"cluster2\"},\n\t\tAuthentication: Authentication{\n\t\t\tBasicAuthentication: basicAuth(\"sourceUser\", \"sourcePass\", \"\"),\n\t\t},\n\t\tSniffing: Sniffing{\n\t\t\tEnabled:  true,\n\t\t\tUseHTTPS: true,\n\t\t},\n\t\tMaxSpanAge:               100,\n\t\tAdaptiveSamplingLookback: 50,\n\t\tIndices: Indices{\n\t\t\tIndexPrefix: \"hello\",\n\t\t\tSpans: IndexOptions{\n\t\t\t\tShards:   5,\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tPriority: 10,\n\t\t\t},\n\t\t\tServices: IndexOptions{\n\t\t\t\tShards:   5,\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tPriority: 20,\n\t\t\t},\n\t\t\tDependencies: IndexOptions{\n\t\t\t\tShards:   5,\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tPriority: 30,\n\t\t\t},\n\t\t\tSampling: IndexOptions{},\n\t\t},\n\t\tBulkProcessing: BulkProcessing{\n\t\t\tMaxBytes:      1000,\n\t\t\tWorkers:       10,\n\t\t\tMaxActions:    100,\n\t\t\tFlushInterval: 30,\n\t\t},\n\t\tTags:          TagsAsFields{AllAsFields: true, DotReplacement: \"dot\", Include: \"include\", File: \"file\"},\n\t\tMaxDocCount:   10000,\n\t\tLogLevel:      \"info\",\n\t\tSendGetBodyAs: \"json\",\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\ttarget   *Configuration\n\t\texpected *Configuration\n\t}{\n\t\t{\n\t\t\tname: \"All Defaults Applied except PriorityDependenciesTemplate\",\n\t\t\ttarget: &Configuration{\n\t\t\t\tIndices: Indices{\n\t\t\t\t\tDependencies: IndexOptions{\n\t\t\t\t\t\tPriority: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, // All fields are empty\n\t\t\texpected: source,\n\t\t},\n\t\t{\n\t\t\tname: \"Some Defaults Applied\",\n\t\t\ttarget: &Configuration{\n\t\t\t\tRemoteReadClusters: []string{\"customCluster\"},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"customUser\", \"\", \"\"),\n\t\t\t\t},\n\t\t\t\tIndices: Indices{\n\t\t\t\t\tSpans: IndexOptions{\n\t\t\t\t\t\tPriority: 10,\n\t\t\t\t\t},\n\t\t\t\t\tServices: IndexOptions{\n\t\t\t\t\t\tPriority: 20,\n\t\t\t\t\t},\n\t\t\t\t\tDependencies: IndexOptions{\n\t\t\t\t\t\tPriority: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// Other fields left default\n\t\t\t},\n\t\t\texpected: &Configuration{\n\t\t\t\tRemoteReadClusters: []string{\"customCluster\"},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"customUser\", \"sourcePass\", \"\"),\n\t\t\t\t},\n\t\t\t\tSniffing: Sniffing{\n\t\t\t\t\tEnabled:  true,\n\t\t\t\t\tUseHTTPS: true,\n\t\t\t\t},\n\t\t\t\tMaxSpanAge:               100,\n\t\t\t\tAdaptiveSamplingLookback: 50,\n\t\t\t\tIndices: Indices{\n\t\t\t\t\tIndexPrefix: \"hello\",\n\t\t\t\t\tSpans: IndexOptions{\n\t\t\t\t\t\tShards:   5,\n\t\t\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\t\t\tPriority: 10,\n\t\t\t\t\t},\n\t\t\t\t\tServices: IndexOptions{\n\t\t\t\t\t\tShards:   5,\n\t\t\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\t\t\tPriority: 20,\n\t\t\t\t\t},\n\t\t\t\t\tDependencies: IndexOptions{\n\t\t\t\t\t\tShards:   5,\n\t\t\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\t\t\tPriority: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes:      1000,\n\t\t\t\t\tWorkers:       10,\n\t\t\t\t\tMaxActions:    100,\n\t\t\t\t\tFlushInterval: 30,\n\t\t\t\t},\n\t\t\t\tTags:          TagsAsFields{AllAsFields: true, DotReplacement: \"dot\", Include: \"include\", File: \"file\"},\n\t\t\t\tMaxDocCount:   10000,\n\t\t\t\tLogLevel:      \"info\",\n\t\t\t\tSendGetBodyAs: \"json\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"No Defaults Applied\",\n\t\t\ttarget: &Configuration{\n\t\t\t\tRemoteReadClusters: []string{\"cluster1\", \"cluster2\"},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"sourceUser\", \"sourcePass\", \"\"),\n\t\t\t\t},\n\t\t\t\tSniffing: Sniffing{\n\t\t\t\t\tEnabled:  true,\n\t\t\t\t\tUseHTTPS: true,\n\t\t\t\t},\n\t\t\t\tMaxSpanAge:               100,\n\t\t\t\tAdaptiveSamplingLookback: 50,\n\t\t\t\tIndices: Indices{\n\t\t\t\t\tIndexPrefix: \"hello\",\n\t\t\t\t\tSpans: IndexOptions{\n\t\t\t\t\t\tShards:   5,\n\t\t\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\t\t\tPriority: 10,\n\t\t\t\t\t},\n\t\t\t\t\tServices: IndexOptions{\n\t\t\t\t\t\tShards:   5,\n\t\t\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\t\t\tPriority: 20,\n\t\t\t\t\t},\n\t\t\t\t\tDependencies: IndexOptions{\n\t\t\t\t\t\tShards:   5,\n\t\t\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\t\t\tPriority: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tBulkProcessing: BulkProcessing{\n\t\t\t\t\tMaxBytes:      1000,\n\t\t\t\t\tWorkers:       10,\n\t\t\t\t\tMaxActions:    100,\n\t\t\t\t\tFlushInterval: 30,\n\t\t\t\t},\n\t\t\t\tTags:          TagsAsFields{AllAsFields: true, DotReplacement: \"dot\", Include: \"include\", File: \"file\"},\n\t\t\t\tMaxDocCount:   10000,\n\t\t\t\tLogLevel:      \"info\",\n\t\t\t\tSendGetBodyAs: \"json\",\n\t\t\t},\n\t\t\texpected: source,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttest.target.ApplyDefaults(source)\n\t\t\trequire.Equal(t, test.expected, test.target)\n\t\t})\n\t}\n}\n\nfunc TestApplyDefaults_Auth(t *testing.T) {\n\tsource := &Configuration{\n\t\tAuthentication: Authentication{\n\t\t\tBasicAuthentication: basicAuth(\"sourceUser\", \"sourcePass\", \"\"),\n\t\t},\n\t}\n\n\ttarget := &Configuration{\n\t\tAuthentication: Authentication{\n\t\t\tBasicAuthentication: basicAuth(\"\", \"\", \"\"),\n\t\t},\n\t}\n\n\texpected := &Configuration{\n\t\tAuthentication: Authentication{\n\t\t\tBasicAuthentication: basicAuth(\"sourceUser\", \"sourcePass\", \"\"),\n\t\t},\n\t}\n\n\ttarget.ApplyDefaults(source)\n\trequire.Equal(t, expected, target)\n}\n\nfunc TestTagKeysAsFields(t *testing.T) {\n\tconst (\n\t\tpwd1 = \"tag1\\ntag2\"\n\t)\n\n\tpwdFile := filepath.Join(t.TempDir(), \"pwd\")\n\trequire.NoError(t, os.WriteFile(pwdFile, []byte(pwd1), 0o600))\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       *Configuration\n\t\texpectedTags []string\n\t\texpectError  bool\n\t}{\n\t\t{\n\t\t\tname: \"File with tags\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tTags: TagsAsFields{\n\t\t\t\t\tFile:    pwdFile,\n\t\t\t\t\tInclude: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTags: []string{\"tag1\", \"tag2\"},\n\t\t\texpectError:  false,\n\t\t},\n\t\t{\n\t\t\tname: \"include with tags\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tTags: TagsAsFields{\n\t\t\t\t\tFile:    \"\",\n\t\t\t\t\tInclude: \"cmdtag1,cmdtag2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTags: []string{\"cmdtag1\", \"cmdtag2\"},\n\t\t\texpectError:  false,\n\t\t},\n\t\t{\n\t\t\tname: \"File and include with tags\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tTags: TagsAsFields{\n\t\t\t\t\tFile:    pwdFile,\n\t\t\t\t\tInclude: \"cmdtag1,cmdtag2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTags: []string{\"tag1\", \"tag2\", \"cmdtag1\", \"cmdtag2\"},\n\t\t\texpectError:  false,\n\t\t},\n\t\t{\n\t\t\tname: \"File read error\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tTags: TagsAsFields{\n\t\t\t\t\tFile:    \"/invalid/path/to/file.txt\",\n\t\t\t\t\tInclude: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTags: nil,\n\t\t\texpectError:  true,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty file and params\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tTags: TagsAsFields{\n\t\t\t\t\tFile:    \"\",\n\t\t\t\t\tInclude: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTags: nil,\n\t\t\texpectError:  false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttags, err := test.config.TagKeysAsFields()\n\t\t\tif test.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Nil(t, tags)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.ElementsMatch(t, test.expectedTags, tags)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRolloverFrequencyAsNegativeDuration(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tindexFrequency string\n\t\texpected       time.Duration\n\t}{\n\t\t{\n\t\t\tname:           \"hourly jaeger-span\",\n\t\t\tindexFrequency: \"hour\",\n\t\t\texpected:       -1 * time.Hour,\n\t\t},\n\t\t{\n\t\t\tname:           \"daily jaeger-span\",\n\t\t\tindexFrequency: \"daily\",\n\t\t\texpected:       -24 * time.Hour,\n\t\t},\n\t\t{\n\t\t\tname:           \"empty jaeger-span\",\n\t\t\tindexFrequency: \"\",\n\t\t\texpected:       -24 * time.Hour,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot := RolloverFrequencyAsNegativeDuration(test.indexFrequency)\n\t\t\trequire.Equal(t, test.expected, got)\n\t\t})\n\t}\n}\n\nfunc TestValidate(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tconfig        *Configuration\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"All valid input are set\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers: []string{\"localhost:8000/dummyserver\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"no valid input are set\",\n\t\t\tconfig:        &Configuration{},\n\t\t\texpectedError: \"Servers: non zero value required\",\n\t\t},\n\t\t{\n\t\t\tname:          \"ilm disabled and read-write aliases enabled error\",\n\t\t\tconfig:        &Configuration{Servers: []string{\"localhost:8000/dummyserver\"}, UseILM: true},\n\t\t\texpectedError: \"UseILM must always be used in conjunction with UseReadWriteAliases to ensure ES writers and readers refer to the single index mapping\",\n\t\t},\n\t\t{\n\t\t\tname:          \"ilm and create templates enabled\",\n\t\t\tconfig:        &Configuration{Servers: []string{\"localhost:8000/dummyserver\"}, UseILM: true, CreateIndexTemplates: true, UseReadWriteAliases: true},\n\t\t\texpectedError: \"when UseILM is set true, CreateIndexTemplates must be set to false and index templates must be created by init process of es-rollover app\",\n\t\t},\n\t\t{\n\t\t\tname: \"explicit span aliases without UseReadWriteAliases\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:        []string{\"localhost:8000/dummyserver\"},\n\t\t\t\tSpanReadAlias:  \"custom-span-read\",\n\t\t\t\tSpanWriteAlias: \"custom-span-write\",\n\t\t\t},\n\t\t\texpectedError: \"explicit aliases (span_read_alias, span_write_alias, service_read_alias, service_write_alias) require UseReadWriteAliases to be true\",\n\t\t},\n\t\t{\n\t\t\tname: \"only span read alias set\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:             []string{\"localhost:8000/dummyserver\"},\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanReadAlias:       \"custom-span-read\",\n\t\t\t},\n\t\t\texpectedError: \"both span_read_alias and span_write_alias must be set together\",\n\t\t},\n\t\t{\n\t\t\tname: \"only service write alias set\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:             []string{\"localhost:8000/dummyserver\"},\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tServiceWriteAlias:   \"custom-service-write\",\n\t\t\t},\n\t\t\texpectedError: \"both service_read_alias and service_write_alias must be set together\",\n\t\t},\n\t\t{\n\t\t\tname: \"all explicit aliases with UseReadWriteAliases is valid\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:             []string{\"localhost:8000/dummyserver\"},\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanReadAlias:       \"custom-span-read\",\n\t\t\t\tSpanWriteAlias:      \"custom-span-write\",\n\t\t\t\tServiceReadAlias:    \"custom-service-read\",\n\t\t\t\tServiceWriteAlias:   \"custom-service-write\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"only span aliases with UseReadWriteAliases is valid\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:             []string{\"localhost:8000/dummyserver\"},\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanReadAlias:       \"custom-span-read\",\n\t\t\t\tSpanWriteAlias:      \"custom-span-write\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"explicit aliases with IndexPrefix is valid\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tServers:             []string{\"localhost:8000/dummyserver\"},\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanReadAlias:       \"custom-span-read\",\n\t\t\t\tSpanWriteAlias:      \"custom-span-write\",\n\t\t\t\tServiceReadAlias:    \"custom-service-read\",\n\t\t\t\tServiceWriteAlias:   \"custom-service-write\",\n\t\t\t\tIndices: Indices{\n\t\t\t\t\tIndexPrefix: \"prod\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot := test.config.Validate()\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, got, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestApplyForIndexPrefix(t *testing.T) {\n\ttests := []struct {\n\t\ttestName     string\n\t\tprefix       IndexPrefix\n\t\tname         string\n\t\texpectedName string\n\t}{\n\t\t{\n\t\t\ttestName:     \"no prefix\",\n\t\t\tprefix:       \"\",\n\t\t\tname:         \"hello\",\n\t\t\texpectedName: \"hello\",\n\t\t},\n\t\t{\n\t\t\ttestName:     \"empty name\",\n\t\t\tprefix:       \"bye\",\n\t\t\tname:         \"\",\n\t\t\texpectedName: \"bye-\",\n\t\t},\n\t\t{\n\t\t\ttestName:     \"separator suffix\",\n\t\t\tprefix:       \"bye-\",\n\t\t\tname:         \"hello\",\n\t\t\texpectedName: \"bye-hello\",\n\t\t},\n\t\t{\n\t\t\ttestName:     \"no separator suffix\",\n\t\t\tprefix:       \"bye\",\n\t\t\tname:         \"hello\",\n\t\t\texpectedName: \"bye-hello\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.testName, func(t *testing.T) {\n\t\t\tgot := test.prefix.Apply(test.name)\n\t\t\trequire.Equal(t, test.expectedName, got)\n\t\t})\n\t}\n}\n\nfunc TestHandleBulkAfterCallback_ErrorMetricsEmitted(t *testing.T) {\n\tmf := metricstest.NewFactory(time.Minute)\n\tsm := spanstoremetrics.NewWriter(mf, \"bulk_index\")\n\tlogger := zap.NewNop()\n\tdefer mf.Stop()\n\n\tvar m sync.Map\n\tbatchID := int64(1)\n\tstart := time.Now().Add(-100 * time.Millisecond)\n\tm.Store(batchID, start)\n\n\tfakeRequests := []elastic.BulkableRequest{nil, nil}\n\tresponse := &elastic.BulkResponse{\n\t\tErrors: true,\n\t\tItems: []map[string]*elastic.BulkResponseItem{\n\t\t\t{\n\t\t\t\t\"index\": {\n\t\t\t\t\tStatus: 500,\n\t\t\t\t\tError:  &elastic.ErrorDetails{Type: \"server_error\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"index\": {\n\t\t\t\t\tStatus: 200,\n\t\t\t\t\tError:  nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbcb := bulkCallback{\n\t\tsm:     sm,\n\t\tlogger: logger,\n\t}\n\tbcb.invoke(batchID, fakeRequests, response, assert.AnError)\n\n\tmf.AssertCounterMetrics(t,\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.errors\",\n\t\t\tValue: 1,\n\t\t},\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.inserts\",\n\t\t\tValue: 1,\n\t\t},\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.attempts\",\n\t\t\tValue: 2,\n\t\t},\n\t)\n}\n\nfunc TestHandleBulkAfterCallback_MissingStartTime(t *testing.T) {\n\tmf := metricstest.NewFactory(time.Minute)\n\tsm := spanstoremetrics.NewWriter(mf, \"bulk_index\")\n\tlogger := zap.NewNop()\n\tdefer mf.Stop()\n\n\tbatchID := int64(42) // assign any value which is not stored in the map\n\tfakeRequests := []elastic.BulkableRequest{nil}\n\tresponse := &elastic.BulkResponse{\n\t\tErrors: true,\n\t\tItems: []map[string]*elastic.BulkResponseItem{\n\t\t\t{\n\t\t\t\t\"index\": {\n\t\t\t\t\tStatus: 500,\n\t\t\t\t\tError:  &elastic.ErrorDetails{Type: \"mock_error\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbcb := bulkCallback{\n\t\tsm:     sm,\n\t\tlogger: logger,\n\t}\n\tbcb.invoke(batchID, fakeRequests, response, assert.AnError)\n\n\tmf.AssertCounterMetrics(t,\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.errors\",\n\t\t\tValue: 1,\n\t\t},\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.inserts\",\n\t\t\tValue: 0,\n\t\t},\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.attempts\",\n\t\t\tValue: 1,\n\t\t},\n\t)\n}\n\nfunc TestGetConfigOptions(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tbearerTokenFile := filepath.Join(tmpDir, \"bearertoken\")\n\tos.WriteFile(bearerTokenFile, []byte(\"file-bearer-token\"), 0o600)\n\n\ttests := []struct {\n\t\tname            string\n\t\tcfg             *Configuration\n\t\tctx             context.Context\n\t\tprepare         func()\n\t\twantErr         bool\n\t\twantErrContains string\n\t}{\n\t\t{\n\t\t\tname: \"BearerToken context propagation\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(\"\", true),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t},\n\t\t\tctx:     bearertoken.ContextWithBearerToken(context.Background(), \"context-bearer-token\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"BearerToken file and context both enabled\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(bearerTokenFile, true),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t},\n\t\t\tctx:     bearertoken.ContextWithBearerToken(context.Background(), \"context-bearer-token\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"BearerToken file error\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tTLS:      configtls.ClientConfig{Insecure: true},\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(\"/does/not/exist/token\", false),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t},\n\t\t\tctx:             context.Background(),\n\t\t\twantErr:         true,\n\t\t\twantErrContains: \"no such file or directory\",\n\t\t},\n\t\t{\n\t\t\tname: \"No auth configured\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t},\n\t\t\tctx:     context.Background(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"BasicAuth password file error\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"testuser\", \"\", \"/does/not/exist\"),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t},\n\t\t\tctx:             context.Background(),\n\t\t\twantErr:         true,\n\t\t\twantErrContains: \"failed to initialize basic authentication\",\n\t\t},\n\n\t\t{\n\t\t\tname: \"BasicAuth both Password and PasswordFilePath set\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"testuser\", \"secret\", \"/some/file/path\"),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"info\",\n\t\t\t},\n\t\t\tctx:             context.Background(),\n\t\t\twantErr:         true,\n\t\t\twantErrContains: \"failed to initialize basic authentication\",\n\t\t},\n\n\t\t{\n\t\t\tname: \"Invalid log level triggers addLoggerOptions error\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:  []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBasicAuthentication: basicAuth(\"user\", \"secret\", \"\"),\n\t\t\t\t},\n\t\t\t\tLogLevel: \"invalid\",\n\t\t\t},\n\t\t\tctx:             context.Background(),\n\t\t\twantErr:         true,\n\t\t\twantErrContains: \"unrecognized log-level\",\n\t\t},\n\t\t{\n\t\t\tname: \"Health check disabled for context-only auth\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:            []string{\"http://localhost:9200\"},\n\t\t\t\tLogLevel:           \"info\",\n\t\t\t\tDisableHealthCheck: false, // Should be overridden by context-only auth\n\t\t\t\tSniffing:           Sniffing{Enabled: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(\"\", true),\n\t\t\t\t},\n\t\t\t},\n\t\t\tctx:     bearertoken.ContextWithBearerToken(context.Background(), \"context-bearer-token\"),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Health check disabled explicitly\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:            []string{\"http://localhost:9200\"},\n\t\t\t\tLogLevel:           \"info\",\n\t\t\t\tDisableHealthCheck: true,\n\t\t\t\tSniffing:           Sniffing{Enabled: false},\n\t\t\t},\n\t\t\tctx:     context.Background(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP compression and custom SendGetBodyAs\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers:         []string{\"http://localhost:9200\"},\n\t\t\t\tLogLevel:        \"info\",\n\t\t\t\tHTTPCompression: true,\n\t\t\t\tSendGetBodyAs:   \"POST\",\n\t\t\t\tSniffing:        Sniffing{Enabled: true, UseHTTPS: true},\n\t\t\t},\n\t\t\tctx:     context.Background(),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tlogger := zap.NewNop()\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif tt.prepare != nil {\n\t\t\t\ttt.prepare()\n\t\t\t}\n\n\t\t\toptions, err := tt.cfg.getConfigOptions(tt.ctx, logger, nil)\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tif tt.wantErrContains != \"\" {\n\t\t\t\t\trequire.Contains(t, err.Error(), tt.wantErrContains)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, options)\n\t\t\t\trequire.NotEmpty(t, options, \"Should have at least basic ES options\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetESOptions(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\tcfg                *Configuration\n\t\tdisableHealthCheck bool\n\t\twantErr            bool\n\t\tvalidateOptions    func(t *testing.T, options []elastic.ClientOptionFunc)\n\t}{\n\t\t{\n\t\t\tname: \"Basic configuration\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers: []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{\n\t\t\t\t\tEnabled:  true,\n\t\t\t\t\tUseHTTPS: false,\n\t\t\t\t},\n\t\t\t\tHTTPCompression: true,\n\t\t\t\tSendGetBodyAs:   \"POST\",\n\t\t\t},\n\t\t\tdisableHealthCheck: false,\n\t\t\twantErr:            false,\n\t\t\tvalidateOptions: func(t *testing.T, options []elastic.ClientOptionFunc) {\n\t\t\t\trequire.NotNil(t, options)\n\t\t\t\trequire.NotEmpty(t, options, \"Expected non-empty options slice\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTPS configuration\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers: []string{\"https://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{\n\t\t\t\t\tEnabled:  false,\n\t\t\t\t\tUseHTTPS: true,\n\t\t\t\t},\n\t\t\t\tHTTPCompression: false,\n\t\t\t\tSendGetBodyAs:   \"\",\n\t\t\t},\n\t\t\tdisableHealthCheck: true,\n\t\t\twantErr:            false,\n\t\t\tvalidateOptions: func(t *testing.T, options []elastic.ClientOptionFunc) {\n\t\t\t\trequire.NotNil(t, options)\n\t\t\t\trequire.NotEmpty(t, options, \"Expected non-empty options slice\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Minimal configuration\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers: []string{\"http://localhost:9200\"},\n\t\t\t\tSniffing: Sniffing{\n\t\t\t\t\tEnabled:  false,\n\t\t\t\t\tUseHTTPS: false,\n\t\t\t\t},\n\t\t\t\tHTTPCompression: false,\n\t\t\t\tSendGetBodyAs:   \"\",\n\t\t\t},\n\t\t\tdisableHealthCheck: false,\n\t\t\twantErr:            false,\n\t\t\tvalidateOptions: func(t *testing.T, options []elastic.ClientOptionFunc) {\n\t\t\t\trequire.NotNil(t, options)\n\t\t\t\trequire.NotEmpty(t, options, \"Expected non-empty options slice\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple servers\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tServers: []string{\n\t\t\t\t\t\"http://localhost:9200\",\n\t\t\t\t\t\"http://localhost:9201\",\n\t\t\t\t\t\"http://localhost:9202\",\n\t\t\t\t},\n\t\t\t\tHealthCheckTimeoutStartup: 10 * time.Millisecond,\n\t\t\t\tSniffing: Sniffing{\n\t\t\t\t\tEnabled:  true,\n\t\t\t\t\tUseHTTPS: false,\n\t\t\t\t},\n\t\t\t\tHTTPCompression: true,\n\t\t\t\tSendGetBodyAs:   \"GET\",\n\t\t\t},\n\t\t\tdisableHealthCheck: false,\n\t\t\twantErr:            false,\n\t\t\tvalidateOptions: func(t *testing.T, options []elastic.ClientOptionFunc) {\n\t\t\t\trequire.NotNil(t, options)\n\t\t\t\trequire.NotEmpty(t, options, \"Expected non-empty options slice\")\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\toptions := tt.cfg.getESOptions(tt.disableHealthCheck)\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Fail(t, \"Test case expects an error, but getESOptions does not return one.\")\n\t\t\t} else if tt.validateOptions != nil {\n\t\t\t\ttt.validateOptions(t, options)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetConfigOptionsIntegration(t *testing.T) {\n\t// Test that getConfigOptions properly integrates with getESOptions\n\tcfg := &Configuration{\n\t\tServers: []string{\"http://localhost:9200\"},\n\t\tSniffing: Sniffing{\n\t\t\tEnabled:  true,\n\t\t\tUseHTTPS: false,\n\t\t},\n\t\tHTTPCompression: true,\n\t\tSendGetBodyAs:   \"POST\",\n\t\tLogLevel:        \"info\",\n\t\tQueryTimeout:    30 * time.Second,\n\t\tAuthentication: Authentication{\n\t\t\tBasicAuthentication: basicAuth(\"testuser\", \"testpass\", \"\"),\n\t\t},\n\t}\n\n\tlogger := zap.NewNop()\n\toptions, err := cfg.getConfigOptions(context.Background(), logger, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, options)\n\trequire.Greater(t, len(options), 5, \"Should have basic ES options plus additional config options\")\n}\n\nfunc TestGetHTTPRoundTripper(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tbearerTokenFile := filepath.Join(tmpDir, \"bearertoken\")\n\trequire.NoError(t, os.WriteFile(bearerTokenFile, []byte(\"file-bearer-token\"), 0o600))\n\n\ttests := []struct {\n\t\tname            string\n\t\tcfg             *Configuration\n\t\tctx             context.Context\n\t\twantErrContains string\n\t\tvalidate        func(t *testing.T, rt http.RoundTripper)\n\t}{\n\t\t{\n\t\t\tname: \"Secure mode without auth\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tTLS: configtls.ClientConfig{Insecure: false},\n\t\t\t},\n\t\t\tctx: context.Background(),\n\t\t\tvalidate: func(t *testing.T, rt http.RoundTripper) {\n\t\t\t\tassert.NotNil(t, rt)\n\t\t\t\t_, ok := rt.(*auth.RoundTripper)\n\t\t\t\tassert.False(t, ok, \"Should not be an auth round tripper\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Insecure mode without auth\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tTLS: configtls.ClientConfig{Insecure: true},\n\t\t\t},\n\t\t\tctx: context.Background(),\n\t\t\tvalidate: func(t *testing.T, rt http.RoundTripper) {\n\t\t\t\tassert.NotNil(t, rt)\n\t\t\t\ttransport, ok := rt.(*http.Transport)\n\t\t\t\trequire.True(t, ok)\n\t\t\t\tassert.True(t, transport.TLSClientConfig.InsecureSkipVerify)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Bearer auth not applicable (empty config)\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tTLS: configtls.ClientConfig{Insecure: true},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(\"\", false),\n\t\t\t\t},\n\t\t\t},\n\t\t\tctx: context.Background(),\n\t\t\tvalidate: func(t *testing.T, rt http.RoundTripper) {\n\t\t\t\tassert.NotNil(t, rt)\n\t\t\t\t// Should be plain transport since auth is not applicable\n\t\t\t\t_, ok := rt.(*auth.RoundTripper)\n\t\t\t\tassert.False(t, ok, \"Should not be an auth round tripper when config is not applicable\")\n\n\t\t\t\ttransport, ok := rt.(*http.Transport)\n\t\t\t\tassert.True(t, ok, \"Should be plain http.Transport\")\n\t\t\t\tassert.True(t, transport.TLSClientConfig.InsecureSkipVerify)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Secure mode with bearer token from file\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tTLS: configtls.ClientConfig{Insecure: false},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(bearerTokenFile, false),\n\t\t\t\t},\n\t\t\t},\n\t\t\tctx: context.Background(),\n\t\t\tvalidate: func(t *testing.T, rt http.RoundTripper) {\n\t\t\t\tassert.NotNil(t, rt)\n\t\t\t\tauthRT, ok := rt.(*auth.RoundTripper)\n\t\t\t\trequire.True(t, ok, \"Should be an auth round tripper\")\n\t\t\t\trequire.Len(t, authRT.Auths, 1)\n\t\t\t\tassert.Equal(t, \"Bearer\", authRT.Auths[0].Scheme)\n\t\t\t\tassert.NotNil(t, authRT.Auths[0].TokenFn)\n\t\t\t\tassert.Equal(t, \"file-bearer-token\", authRT.Auths[0].TokenFn())\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Insecure mode with bearer token from context\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tTLS: configtls.ClientConfig{Insecure: true},\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(\"\", true),\n\t\t\t\t},\n\t\t\t},\n\t\t\tctx: bearertoken.ContextWithBearerToken(context.Background(), \"context-bearer-token\"),\n\t\t\tvalidate: func(t *testing.T, rt http.RoundTripper) {\n\t\t\t\tassert.NotNil(t, rt)\n\t\t\t\tauthRT, ok := rt.(*auth.RoundTripper)\n\t\t\t\trequire.True(t, ok, \"Should be an auth round tripper\")\n\t\t\t\trequire.Len(t, authRT.Auths, 1)\n\t\t\t\tassert.Equal(t, \"Bearer\", authRT.Auths[0].Scheme)\n\t\t\t\tassert.NotNil(t, authRT.Auths[0].FromCtx)\n\n\t\t\t\ttransport, ok := authRT.Transport.(*http.Transport)\n\t\t\t\trequire.True(t, ok)\n\t\t\t\tassert.True(t, transport.TLSClientConfig.InsecureSkipVerify)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"BearerToken file error\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tAuthentication: Authentication{\n\t\t\t\t\tBearerTokenAuth: bearerAuth(\"/does/not/exist/token\", false),\n\t\t\t\t},\n\t\t\t},\n\t\t\tctx:             context.Background(),\n\t\t\twantErrContains: \"no such file or directory\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid TLS config should fail\",\n\t\t\tcfg: &Configuration{\n\t\t\t\tTLS: configtls.ClientConfig{\n\t\t\t\t\tInsecure: false,\n\t\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\t\tCAFile: \"/does/not/exist/ca.pem\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tctx:             context.Background(),\n\t\t\twantErrContains: \"failed to load TLS config\",\n\t\t},\n\t}\n\n\tlogger := zap.NewNop()\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trt, err := GetHTTPRoundTripper(tt.ctx, tt.cfg, logger, nil)\n\t\t\tif tt.wantErrContains != \"\" {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tt.wantErrContains)\n\t\t\t\tassert.Nil(t, rt)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\ttt.validate(t, rt)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test GetHTTPRoundTripper with httpAuth error\nfunc TestGetHTTPRoundTripperWithHTTPAuthError(t *testing.T) {\n\tctx := context.Background()\n\tlogger := zap.NewNop()\n\t// Create a mock httpAuth that will fail on RoundTripper wrapping\n\tmockAuth := &mockFailingHTTPAuth{}\n\n\tc := &Configuration{\n\t\tServers:  []string{\"http://localhost:9200\"},\n\t\tLogLevel: \"error\",\n\t\tTLS:      configtls.ClientConfig{Insecure: true},\n\t}\n\n\t_, err := GetHTTPRoundTripper(ctx, c, logger, mockAuth)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"failed to wrap round tripper with HTTP authenticator\")\n}\n\n// Mock failing HTTP authenticator\ntype mockFailingHTTPAuth struct{}\n\nfunc (*mockFailingHTTPAuth) RoundTripper(_ http.RoundTripper) (http.RoundTripper, error) {\n\treturn nil, errors.New(\"mock authenticator error\")\n}\n\nfunc TestGetHTTPRoundTripperWrappingError(t *testing.T) {\n\tctx := context.Background()\n\tlogger := zap.NewNop()\n\n\t// Create a mock failing HTTP authenticator\n\tmockAuth := &mockFailingHTTPAuthWrapper{}\n\n\tc := &Configuration{\n\t\tServers:  []string{\"http://localhost:9200\"},\n\t\tLogLevel: \"error\",\n\t\tTLS:      configtls.ClientConfig{Insecure: true},\n\t}\n\n\t_, err := GetHTTPRoundTripper(ctx, c, logger, mockAuth)\n\trequire.Error(t, err)\n\trequire.ErrorContains(t, err, \"failed to wrap round tripper with HTTP authenticator\")\n}\n\n// mockFailingHTTPAuthWrapper mocks a failing HTTP authenticator for wrapping tests\ntype mockFailingHTTPAuthWrapper struct{}\n\nfunc (*mockFailingHTTPAuthWrapper) RoundTripper(_ http.RoundTripper) (http.RoundTripper, error) {\n\treturn nil, errors.New(\"wrapping error\")\n}\n\n// Test GetHTTPRoundTripper with successful httpAuth wrapping\nfunc TestGetHTTPRoundTripperWithHTTPAuthSuccess(t *testing.T) {\n\tctx := context.Background()\n\tlogger := zap.NewNop()\n\n\t// Create a mock httpAuth that will succeed\n\tmockAuth := &mockSuccessfulHTTPAuth{}\n\n\tc := &Configuration{\n\t\tServers:  []string{\"http://localhost:9200\"},\n\t\tLogLevel: \"error\",\n\t\tTLS:      configtls.ClientConfig{Insecure: true},\n\t}\n\n\trt, err := GetHTTPRoundTripper(ctx, c, logger, mockAuth)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, rt)\n\twrappedRT, ok := rt.(*mockWrappedRoundTripper)\n\trequire.True(t, ok, \"Should be wrapped round tripper\")\n\trequire.NotNil(t, wrappedRT)\n}\n\n// Mock successful HTTP authenticator\ntype mockSuccessfulHTTPAuth struct{}\n\nfunc (*mockSuccessfulHTTPAuth) RoundTripper(rt http.RoundTripper) (http.RoundTripper, error) {\n\treturn &mockWrappedRoundTripper{base: rt}, nil\n}\n\n// Mock wrapped round tripper\ntype mockWrappedRoundTripper struct {\n\tbase http.RoundTripper\n}\n\nfunc (m *mockWrappedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn m.base.RoundTrip(req)\n}\n\nfunc TestBulkCallbackInvoke_NilResponse(t *testing.T) {\n\tmf := metricstest.NewFactory(time.Minute)\n\tsm := spanstoremetrics.NewWriter(mf, \"bulk_index\")\n\tlogger := zap.NewNop()\n\tdefer mf.Stop()\n\n\tbcb := bulkCallback{\n\t\tsm:     sm,\n\t\tlogger: logger,\n\t}\n\tbcb.invoke(1, []elastic.BulkableRequest{nil}, nil, assert.AnError)\n\n\tmf.AssertCounterMetrics(t,\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.errors\",\n\t\t\tValue: 0,\n\t\t},\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.inserts\",\n\t\t\tValue: 1,\n\t\t},\n\t\tmetricstest.ExpectedMetric{\n\t\t\tName:  \"bulk_index.attempts\",\n\t\t\tValue: 1,\n\t\t},\n\t)\n}\n\nfunc TestCustomHeaders(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tconfig   Configuration\n\t\texpected map[string]string\n\t}{\n\t\t{\n\t\t\tname: \"custom headers are set correctly\",\n\t\t\tconfig: Configuration{\n\t\t\t\tServers: []string{\"http://localhost:9200\"},\n\t\t\t\tCustomHeaders: map[string]string{\n\t\t\t\t\t\"Host\":            \"my-opensearch.amazonaws.com\",\n\t\t\t\t\t\"X-Custom-Header\": \"test-value\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: map[string]string{\n\t\t\t\t\"Host\":            \"my-opensearch.amazonaws.com\",\n\t\t\t\t\"X-Custom-Header\": \"test-value\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty custom headers\",\n\t\t\tconfig: Configuration{\n\t\t\t\tServers:       []string{\"http://localhost:9200\"},\n\t\t\t\tCustomHeaders: map[string]string{},\n\t\t\t},\n\t\t\texpected: map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname: \"nil custom headers\",\n\t\t\tconfig: Configuration{\n\t\t\t\tServers:       []string{\"http://localhost:9200\"},\n\t\t\t\tCustomHeaders: nil,\n\t\t\t},\n\t\t\texpected: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tif test.expected == nil {\n\t\t\t\tassert.Nil(t, test.config.CustomHeaders)\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, test.expected, test.config.CustomHeaders)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestApplyDefaultsCustomHeaders(t *testing.T) {\n\tsource := &Configuration{\n\t\tCustomHeaders: map[string]string{\n\t\t\t\"Host\":            \"source-host\",\n\t\t\t\"X-Custom-Header\": \"source-value\",\n\t\t},\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\ttarget   *Configuration\n\t\texpected map[string]string\n\t}{\n\t\t{\n\t\t\tname:   \"target has no headers, apply from source\",\n\t\t\ttarget: &Configuration{},\n\t\t\texpected: map[string]string{\n\t\t\t\t\"Host\":            \"source-host\",\n\t\t\t\t\"X-Custom-Header\": \"source-value\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"target has headers, keep target headers\",\n\t\t\ttarget: &Configuration{\n\t\t\t\tCustomHeaders: map[string]string{\n\t\t\t\t\t\"Host\": \"target-host\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: map[string]string{\n\t\t\t\t\"Host\": \"target-host\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"target has empty map, keep empty\",\n\t\t\ttarget: &Configuration{\n\t\t\t\tCustomHeaders: map[string]string{},\n\t\t\t},\n\t\t\texpected: map[string]string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttest.target.ApplyDefaults(source)\n\t\t\tassert.Equal(t, test.expected, test.target.CustomHeaders)\n\t\t})\n\t}\n}\n\nfunc TestNewClientWithCustomHeaders(t *testing.T) {\n\theadersSeen := false\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t// Check if custom headers are present\n\t\tif req.Header.Get(\"X-Custom-Header\") == \"custom-value\" {\n\t\t\theadersSeen = true\n\t\t}\n\t\tres.WriteHeader(http.StatusOK)\n\t\tres.Write(mockEsServerResponseWithVersion8)\n\t}))\n\tdefer testServer.Close()\n\n\tconfig := Configuration{\n\t\tServers: []string{testServer.URL},\n\t\tCustomHeaders: map[string]string{\n\t\t\t\"Host\":            \"my-opensearch.amazonaws.com\",\n\t\t\t\"X-Custom-Header\": \"custom-value\",\n\t\t},\n\t\tLogLevel: \"error\",\n\t\tVersion:  8,\n\t}\n\n\tlogger := zap.NewNop()\n\tmetricsFactory := metrics.NullFactory\n\n\tclient, err := NewClient(context.Background(), &config, logger, metricsFactory, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, client)\n\n\t// Verify the configuration has the custom headers set\n\t// Note: The ES v8 client may not send custom headers during the initial ping/health check,\n\t// but they will be available for actual Elasticsearch operations (index, search, etc.)\n\tassert.Equal(t, \"my-opensearch.amazonaws.com\", config.CustomHeaders[\"Host\"])\n\tassert.Equal(t, \"custom-value\", config.CustomHeaders[\"X-Custom-Header\"])\n\n\tif headersSeen {\n\t\tt.Log(\" Custom headers were transmitted in HTTP request\")\n\t} else {\n\t\tt.Log(\"  Custom headers not sent in ping request (expected - will be sent in data operations)\")\n\t}\n\n\terr = client.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/dbmodel/dot_replacer.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport \"strings\"\n\n// DotReplacer replaces the dots in span tags\ntype DotReplacer struct {\n\ttagDotReplacement string\n}\n\n// NewDotReplacer returns an instance of DotReplacer\nfunc NewDotReplacer(tagDotReplacement string) DotReplacer {\n\treturn DotReplacer{tagDotReplacement: tagDotReplacement}\n}\n\n// ReplaceDot replaces dot with dotReplacement\nfunc (dm DotReplacer) ReplaceDot(k string) string {\n\treturn strings.ReplaceAll(k, \".\", dm.tagDotReplacement)\n}\n\n// ReplaceDotReplacement replaces dotReplacement with dot\nfunc (dm DotReplacer) ReplaceDotReplacement(k string) string {\n\treturn strings.ReplaceAll(k, dm.tagDotReplacement, \".\")\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/dbmodel/dot_replacer_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDotReplacement(t *testing.T) {\n\tconverter := NewDotReplacer(\"#\")\n\tk := \"foo.foo\"\n\tassert.Equal(t, k, converter.ReplaceDotReplacement(converter.ReplaceDot(k)))\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/dbmodel/model.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport \"time\"\n\n// ReferenceType is the reference type of one span to another\ntype ReferenceType string\n\n// TraceID is the shared trace ID of all spans in the trace.\ntype TraceID string\n\n// SpanID is the id of a span\ntype SpanID string\n\n// ValueType is the type of a value stored in KeyValue struct.\ntype ValueType string\n\n// Trace is the type of traces\ntype Trace struct {\n\tSpans []Span\n}\n\nconst (\n\t// ChildOf means a span is the child of another span\n\tChildOf ReferenceType = \"CHILD_OF\"\n\t// FollowsFrom means a span follows from another span\n\tFollowsFrom ReferenceType = \"FOLLOWS_FROM\"\n\n\t// StringType indicates a string value stored in KeyValue\n\tStringType ValueType = \"string\"\n\t// BoolType indicates a Boolean value stored in KeyValue\n\tBoolType ValueType = \"bool\"\n\t// Int64Type indicates a 64bit signed integer value stored in KeyValue\n\tInt64Type ValueType = \"int64\"\n\t// Float64Type indicates a 64bit float value stored in KeyValue\n\tFloat64Type ValueType = \"float64\"\n\t// BinaryType indicates an arbitrary byte array stored in KeyValue\n\tBinaryType ValueType = \"binary\"\n)\n\n// Span is ES database representation of the domain span.\ntype Span struct {\n\tTraceID       TraceID     `json:\"traceID\"`\n\tSpanID        SpanID      `json:\"spanID\"`\n\tParentSpanID  SpanID      `json:\"parentSpanID,omitempty\"` // deprecated\n\tFlags         uint32      `json:\"flags,omitempty\"`\n\tOperationName string      `json:\"operationName\"`\n\tReferences    []Reference `json:\"references\"`\n\tStartTime     uint64      `json:\"startTime\"` // microseconds since Unix epoch\n\t// ElasticSearch does not support a UNIX Epoch timestamp in microseconds,\n\t// so Jaeger maps StartTime to a 'long' type. This extra StartTimeMillis field\n\t// works around this issue, enabling timerange queries.\n\tStartTimeMillis uint64     `json:\"startTimeMillis\"`\n\tDuration        uint64     `json:\"duration\"` // microseconds\n\tTags            []KeyValue `json:\"tags\"`\n\t// Alternative representation of tags for better kibana support\n\tTag     map[string]any `json:\"tag,omitempty\"`\n\tLogs    []Log          `json:\"logs\"`\n\tProcess Process        `json:\"process\"`\n}\n\n// Reference is a reference from one span to another\ntype Reference struct {\n\tRefType ReferenceType `json:\"refType\"`\n\tTraceID TraceID       `json:\"traceID\"`\n\tSpanID  SpanID        `json:\"spanID\"`\n}\n\n// Process is the process emitting a set of spans\ntype Process struct {\n\tServiceName string     `json:\"serviceName\"`\n\tTags        []KeyValue `json:\"tags\"`\n\t// Alternative representation of tags for better kibana support\n\tTag map[string]any `json:\"tag,omitempty\"`\n}\n\n// Log is a log emitted in a span\ntype Log struct {\n\tTimestamp uint64     `json:\"timestamp\"`\n\tFields    []KeyValue `json:\"fields\"`\n}\n\n// KeyValue is a key-value pair with typed value.\ntype KeyValue struct {\n\tKey   string    `json:\"key\"`\n\tType  ValueType `json:\"type,omitempty\"`\n\tValue any       `json:\"value\"`\n}\n\n// Service is the JSON struct for service:operation documents in ElasticSearch\ntype Service struct {\n\tServiceName   string `json:\"serviceName\"`\n\tOperationName string `json:\"operationName\"`\n}\n\n// Operation is the struct for span operation properties.\ntype Operation struct {\n\tName     string\n\tSpanKind string\n}\n\n// OperationQueryParameters contains parameters of query operations, empty spanKind means get operations for all kinds of span.\ntype OperationQueryParameters struct {\n\tServiceName string\n\tSpanKind    string\n}\n\n// TraceQueryParameters contains parameters of a trace query.\ntype TraceQueryParameters struct {\n\tServiceName   string\n\tOperationName string\n\tTags          map[string]string\n\tStartTimeMin  time.Time\n\tStartTimeMax  time.Time\n\tDurationMin   time.Duration\n\tDurationMax   time.Duration\n\t// TODO: Rename NumTraces to SearchDepth\n\tNumTraces int\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/dbmodel/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/empty_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/errors.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/olivere/elastic/v7\"\n)\n\n// DetailedError creates a more detailed error if the error stack contains elastic.Error.\n// This is useful because by default olivere/elastic returns errors that print like this:\n//\n//\telastic: Error 400 (Bad Request): all shards failed [type=search_phase_execution_exception]\n//\n// This is pretty useless because it masks the underlying root cause.\n// DetailedError would instead return an error like this:\n//\n//\t<same as above>: RootCause[... detailed error message ...]\nfunc DetailedError(err error) error {\n\tvar esErr *elastic.Error\n\tif errors.As(err, &esErr) {\n\t\tif esErr.Details != nil && len(esErr.Details.RootCause) > 0 {\n\t\t\trc := esErr.Details.RootCause[0]\n\t\t\tif rc != nil {\n\t\t\t\treturn fmt.Errorf(\"%w: RootCause[%s [type=%s]]\", err, rc.Reason, rc.Type)\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/errors_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDetailedError(t *testing.T) {\n\trequire.ErrorContains(t, errors.New(\"some err\"), \"some err\", \"no panic\")\n\n\tesErr := &elastic.Error{\n\t\tStatus: 400,\n\t\tDetails: &elastic.ErrorDetails{\n\t\t\tType:   \"type1\",\n\t\t\tReason: \"useless reason, e.g. all shards failed\",\n\t\t\tRootCause: []*elastic.ErrorDetails{\n\t\t\t\t{\n\t\t\t\t\tType:   \"type2\",\n\t\t\t\t\tReason: \"actual reason\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\trequire.ErrorContains(t, DetailedError(esErr), \"actual reason\")\n\n\tesErr.Details.RootCause[0] = nil\n\trequire.ErrorContains(t, DetailedError(esErr), \"useless reason\")\n\trequire.NotContains(t, DetailedError(esErr).Error(), \"actual reason\")\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/filter/alias.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage filter\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\n// AliasExists check if the some of indices has a certain alias name\nfunc AliasExists(indices []client.Index, aliasName string) bool {\n\taliases := ByAlias(indices, []string{aliasName})\n\treturn len(aliases) > 0\n}\n\n// ByAlias filter indices that have an alias in the array of alias\nfunc ByAlias(indices []client.Index, aliases []string) []client.Index {\n\treturn filterByAliasWithOptions(indices, aliases, false)\n}\n\n// ByAliasExclude filter indices that doesn't have an alias in the array of alias\nfunc ByAliasExclude(indices []client.Index, aliases []string) []client.Index {\n\treturn filterByAliasWithOptions(indices, aliases, true)\n}\n\nfunc filterByAliasWithOptions(indices []client.Index, aliases []string, exclude bool) []client.Index {\n\tvar results []client.Index\n\tfor _, alias := range aliases {\n\t\tfor _, index := range indices {\n\t\t\thasAlias := index.Aliases[alias]\n\t\t\tif hasAlias {\n\t\t\t\tresults = append(results, index)\n\t\t\t}\n\t\t}\n\t}\n\tif exclude {\n\t\treturn exlude(indices, results)\n\t}\n\treturn results\n}\n\nfunc exlude(indices []client.Index, exclusionList []client.Index) []client.Index {\n\texcludedIndices := make([]client.Index, 0, len(indices))\n\tfor _, idx := range indices {\n\t\tif !contains(idx, exclusionList) {\n\t\t\texcludedIndices = append(excludedIndices, idx)\n\t\t}\n\t}\n\treturn excludedIndices\n}\n\nfunc contains(index client.Index, indexList []client.Index) bool {\n\tfor _, idx := range indexList {\n\t\tif idx.Index == index.Index {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/filter/alias_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage filter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nvar indices = []client.Index{\n\t{\n\t\tIndex: \"jaeger-span-0001\",\n\t\tAliases: map[string]bool{\n\t\t\t\"jaeger-span-write\": true,\n\t\t\t\"jaeger-span-read\":  true,\n\t\t},\n\t},\n\t{\n\t\tIndex: \"jaeger-span-0002\",\n\t\tAliases: map[string]bool{\n\t\t\t\"jaeger-span-read\": true,\n\t\t},\n\t},\n\t{\n\t\tIndex: \"jaeger-span-0003\",\n\t\tAliases: map[string]bool{\n\t\t\t\"jaeger-span-read\": true,\n\t\t},\n\t},\n\t{\n\t\tIndex: \"jaeger-span-0004\",\n\t\tAliases: map[string]bool{\n\t\t\t\"jaeger-span-other\": true,\n\t\t},\n\t},\n\t{\n\t\tIndex: \"jaeger-span-0005\",\n\t\tAliases: map[string]bool{\n\t\t\t\"custom-alias\": true,\n\t\t},\n\t},\n\t{\n\t\tIndex: \"jaeger-span-0006\",\n\t},\n}\n\nfunc TestByAlias(t *testing.T) {\n\tfiltered := ByAlias(indices, []string{\"jaeger-span-read\", \"jaeger-span-other\"})\n\texpected := []client.Index{\n\t\t{\n\t\t\tIndex: \"jaeger-span-0001\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-write\": true,\n\t\t\t\t\"jaeger-span-read\":  true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-0002\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-read\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-0003\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-read\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-0004\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-other\": true,\n\t\t\t},\n\t\t},\n\t}\n\tassert.Equal(t, expected, filtered)\n}\n\nfunc TestByAliasExclude(t *testing.T) {\n\tfiltered := ByAliasExclude(indices, []string{\"jaeger-span-read\", \"jaeger-span-other\"})\n\texpected := []client.Index{\n\t\t{\n\t\t\tIndex: \"jaeger-span-0005\",\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"custom-alias\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex: \"jaeger-span-0006\",\n\t\t},\n\t}\n\tassert.Equal(t, expected, filtered)\n}\n\nfunc TestHasAliasEmpty(t *testing.T) {\n\tresult := AliasExists(indices, \"my-unexisting-alias\")\n\tassert.False(t, result)\n\n\tresult = AliasExists(indices, \"custom-alias\")\n\tassert.True(t, result)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/filter/date.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage filter\n\nimport (\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\n// ByDate filter indices by creationTime, return indices that were created before certain date.\nfunc ByDate(indices []client.Index, beforeThisDate time.Time) []client.Index {\n\tvar filtered []client.Index\n\tfor _, in := range indices {\n\t\tif in.CreationTime.Before(beforeThisDate) {\n\t\t\tfiltered = append(filtered, in)\n\t\t}\n\t}\n\treturn filtered\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/filter/date_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage filter\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client\"\n)\n\nfunc TestByDate(t *testing.T) {\n\tbeforeDateFilter := time.Date(2021, 10, 10, 12, 0, 0, 0, time.Local)\n\texpectedIndices := []client.Index{\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0006\",\n\t\t\tCreationTime: time.Date(2021, 7, 7, 7, 10, 10, 10, time.Local),\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0004\",\n\t\t\tCreationTime: time.Date(2021, 9, 16, 11, 0, 0, 0, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-other\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0005\",\n\t\t\tCreationTime: time.Date(2021, 10, 10, 9, 56, 34, 25, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"custom-alias\": true,\n\t\t\t},\n\t\t},\n\t}\n\tindices := []client.Index{\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0006\",\n\t\t\tCreationTime: time.Date(2021, 7, 7, 7, 10, 10, 10, time.Local),\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0004\",\n\t\t\tCreationTime: time.Date(2021, 9, 16, 11, 0, 0, 0, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-other\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0005\",\n\t\t\tCreationTime: time.Date(2021, 10, 10, 9, 56, 34, 25, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"custom-alias\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0001\",\n\t\t\tCreationTime: time.Date(2021, 10, 10, 12, 0, 0, 0, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-write\": true,\n\t\t\t\t\"jaeger-span-read\":  true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0002\",\n\t\t\tCreationTime: time.Date(2021, 11, 10, 12, 30, 0, 0, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-read\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tIndex:        \"jaeger-span-0003\",\n\t\t\tCreationTime: time.Date(2021, 12, 10, 2, 15, 20, 1, time.Local),\n\t\t\tAliases: map[string]bool{\n\t\t\t\t\"jaeger-span-read\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\tresult := ByDate(indices, beforeDateFilter)\n\tassert.Equal(t, expectedIndices, result)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/filter/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage filter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/olivere/elastic/v7\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewClient(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Client {\n\tmock := &Client{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Client is an autogenerated mock type for the Client type\ntype Client struct {\n\tmock.Mock\n}\n\ntype Client_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Client) EXPECT() *Client_Expecter {\n\treturn &Client_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type Client\nfunc (_mock *Client) Close() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Close\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Client_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype Client_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\nfunc (_e *Client_Expecter) Close() *Client_Close_Call {\n\treturn &Client_Close_Call{Call: _e.mock.On(\"Close\")}\n}\n\nfunc (_c *Client_Close_Call) Run(run func()) *Client_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_Close_Call) Return(err error) *Client_Close_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Client_Close_Call) RunAndReturn(run func() error) *Client_Close_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateIndex provides a mock function for the type Client\nfunc (_mock *Client) CreateIndex(index string) elasticsearch.IndicesCreateService {\n\tret := _mock.Called(index)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateIndex\")\n\t}\n\n\tvar r0 elasticsearch.IndicesCreateService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndicesCreateService); ok {\n\t\tr0 = returnFunc(index)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndicesCreateService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_CreateIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateIndex'\ntype Client_CreateIndex_Call struct {\n\t*mock.Call\n}\n\n// CreateIndex is a helper method to define mock.On call\n//   - index string\nfunc (_e *Client_Expecter) CreateIndex(index interface{}) *Client_CreateIndex_Call {\n\treturn &Client_CreateIndex_Call{Call: _e.mock.On(\"CreateIndex\", index)}\n}\n\nfunc (_c *Client_CreateIndex_Call) Run(run func(index string)) *Client_CreateIndex_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_CreateIndex_Call) Return(indicesCreateService elasticsearch.IndicesCreateService) *Client_CreateIndex_Call {\n\t_c.Call.Return(indicesCreateService)\n\treturn _c\n}\n\nfunc (_c *Client_CreateIndex_Call) RunAndReturn(run func(index string) elasticsearch.IndicesCreateService) *Client_CreateIndex_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateTemplate provides a mock function for the type Client\nfunc (_mock *Client) CreateTemplate(id string) elasticsearch.TemplateCreateService {\n\tret := _mock.Called(id)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateTemplate\")\n\t}\n\n\tvar r0 elasticsearch.TemplateCreateService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.TemplateCreateService); ok {\n\t\tr0 = returnFunc(id)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.TemplateCreateService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_CreateTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTemplate'\ntype Client_CreateTemplate_Call struct {\n\t*mock.Call\n}\n\n// CreateTemplate is a helper method to define mock.On call\n//   - id string\nfunc (_e *Client_Expecter) CreateTemplate(id interface{}) *Client_CreateTemplate_Call {\n\treturn &Client_CreateTemplate_Call{Call: _e.mock.On(\"CreateTemplate\", id)}\n}\n\nfunc (_c *Client_CreateTemplate_Call) Run(run func(id string)) *Client_CreateTemplate_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_CreateTemplate_Call) Return(templateCreateService elasticsearch.TemplateCreateService) *Client_CreateTemplate_Call {\n\t_c.Call.Return(templateCreateService)\n\treturn _c\n}\n\nfunc (_c *Client_CreateTemplate_Call) RunAndReturn(run func(id string) elasticsearch.TemplateCreateService) *Client_CreateTemplate_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// DeleteIndex provides a mock function for the type Client\nfunc (_mock *Client) DeleteIndex(index string) elasticsearch.IndicesDeleteService {\n\tret := _mock.Called(index)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for DeleteIndex\")\n\t}\n\n\tvar r0 elasticsearch.IndicesDeleteService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndicesDeleteService); ok {\n\t\tr0 = returnFunc(index)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndicesDeleteService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_DeleteIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteIndex'\ntype Client_DeleteIndex_Call struct {\n\t*mock.Call\n}\n\n// DeleteIndex is a helper method to define mock.On call\n//   - index string\nfunc (_e *Client_Expecter) DeleteIndex(index interface{}) *Client_DeleteIndex_Call {\n\treturn &Client_DeleteIndex_Call{Call: _e.mock.On(\"DeleteIndex\", index)}\n}\n\nfunc (_c *Client_DeleteIndex_Call) Run(run func(index string)) *Client_DeleteIndex_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_DeleteIndex_Call) Return(indicesDeleteService elasticsearch.IndicesDeleteService) *Client_DeleteIndex_Call {\n\t_c.Call.Return(indicesDeleteService)\n\treturn _c\n}\n\nfunc (_c *Client_DeleteIndex_Call) RunAndReturn(run func(index string) elasticsearch.IndicesDeleteService) *Client_DeleteIndex_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetVersion provides a mock function for the type Client\nfunc (_mock *Client) GetVersion() uint {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetVersion\")\n\t}\n\n\tvar r0 uint\n\tif returnFunc, ok := ret.Get(0).(func() uint); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Get(0).(uint)\n\t}\n\treturn r0\n}\n\n// Client_GetVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVersion'\ntype Client_GetVersion_Call struct {\n\t*mock.Call\n}\n\n// GetVersion is a helper method to define mock.On call\nfunc (_e *Client_Expecter) GetVersion() *Client_GetVersion_Call {\n\treturn &Client_GetVersion_Call{Call: _e.mock.On(\"GetVersion\")}\n}\n\nfunc (_c *Client_GetVersion_Call) Run(run func()) *Client_GetVersion_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_GetVersion_Call) Return(v uint) *Client_GetVersion_Call {\n\t_c.Call.Return(v)\n\treturn _c\n}\n\nfunc (_c *Client_GetVersion_Call) RunAndReturn(run func() uint) *Client_GetVersion_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Index provides a mock function for the type Client\nfunc (_mock *Client) Index() elasticsearch.IndexService {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Index\")\n\t}\n\n\tvar r0 elasticsearch.IndexService\n\tif returnFunc, ok := ret.Get(0).(func() elasticsearch.IndexService); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndexService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_Index_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Index'\ntype Client_Index_Call struct {\n\t*mock.Call\n}\n\n// Index is a helper method to define mock.On call\nfunc (_e *Client_Expecter) Index() *Client_Index_Call {\n\treturn &Client_Index_Call{Call: _e.mock.On(\"Index\")}\n}\n\nfunc (_c *Client_Index_Call) Run(run func()) *Client_Index_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_Index_Call) Return(indexService elasticsearch.IndexService) *Client_Index_Call {\n\t_c.Call.Return(indexService)\n\treturn _c\n}\n\nfunc (_c *Client_Index_Call) RunAndReturn(run func() elasticsearch.IndexService) *Client_Index_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// IndexExists provides a mock function for the type Client\nfunc (_mock *Client) IndexExists(index string) elasticsearch.IndicesExistsService {\n\tret := _mock.Called(index)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for IndexExists\")\n\t}\n\n\tvar r0 elasticsearch.IndicesExistsService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndicesExistsService); ok {\n\t\tr0 = returnFunc(index)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndicesExistsService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_IndexExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IndexExists'\ntype Client_IndexExists_Call struct {\n\t*mock.Call\n}\n\n// IndexExists is a helper method to define mock.On call\n//   - index string\nfunc (_e *Client_Expecter) IndexExists(index interface{}) *Client_IndexExists_Call {\n\treturn &Client_IndexExists_Call{Call: _e.mock.On(\"IndexExists\", index)}\n}\n\nfunc (_c *Client_IndexExists_Call) Run(run func(index string)) *Client_IndexExists_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_IndexExists_Call) Return(indicesExistsService elasticsearch.IndicesExistsService) *Client_IndexExists_Call {\n\t_c.Call.Return(indicesExistsService)\n\treturn _c\n}\n\nfunc (_c *Client_IndexExists_Call) RunAndReturn(run func(index string) elasticsearch.IndicesExistsService) *Client_IndexExists_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// MultiSearch provides a mock function for the type Client\nfunc (_mock *Client) MultiSearch() elasticsearch.MultiSearchService {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for MultiSearch\")\n\t}\n\n\tvar r0 elasticsearch.MultiSearchService\n\tif returnFunc, ok := ret.Get(0).(func() elasticsearch.MultiSearchService); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.MultiSearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_MultiSearch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MultiSearch'\ntype Client_MultiSearch_Call struct {\n\t*mock.Call\n}\n\n// MultiSearch is a helper method to define mock.On call\nfunc (_e *Client_Expecter) MultiSearch() *Client_MultiSearch_Call {\n\treturn &Client_MultiSearch_Call{Call: _e.mock.On(\"MultiSearch\")}\n}\n\nfunc (_c *Client_MultiSearch_Call) Run(run func()) *Client_MultiSearch_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_MultiSearch_Call) Return(multiSearchService elasticsearch.MultiSearchService) *Client_MultiSearch_Call {\n\t_c.Call.Return(multiSearchService)\n\treturn _c\n}\n\nfunc (_c *Client_MultiSearch_Call) RunAndReturn(run func() elasticsearch.MultiSearchService) *Client_MultiSearch_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Search provides a mock function for the type Client\nfunc (_mock *Client) Search(indices ...string) elasticsearch.SearchService {\n\tvar tmpRet mock.Arguments\n\tif len(indices) > 0 {\n\t\ttmpRet = _mock.Called(indices)\n\t} else {\n\t\ttmpRet = _mock.Called()\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Search\")\n\t}\n\n\tvar r0 elasticsearch.SearchService\n\tif returnFunc, ok := ret.Get(0).(func(...string) elasticsearch.SearchService); ok {\n\t\tr0 = returnFunc(indices...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.SearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// Client_Search_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Search'\ntype Client_Search_Call struct {\n\t*mock.Call\n}\n\n// Search is a helper method to define mock.On call\n//   - indices ...string\nfunc (_e *Client_Expecter) Search(indices ...interface{}) *Client_Search_Call {\n\treturn &Client_Search_Call{Call: _e.mock.On(\"Search\",\n\t\tappend([]interface{}{}, indices...)...)}\n}\n\nfunc (_c *Client_Search_Call) Run(run func(indices ...string)) *Client_Search_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []string\n\t\tvar variadicArgs []string\n\t\tif len(args) > 0 {\n\t\t\tvariadicArgs = args[0].([]string)\n\t\t}\n\t\targ0 = variadicArgs\n\t\trun(\n\t\t\targ0...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Client_Search_Call) Return(searchService elasticsearch.SearchService) *Client_Search_Call {\n\t_c.Call.Return(searchService)\n\treturn _c\n}\n\nfunc (_c *Client_Search_Call) RunAndReturn(run func(indices ...string) elasticsearch.SearchService) *Client_Search_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewIndicesExistsService creates a new instance of IndicesExistsService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIndicesExistsService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *IndicesExistsService {\n\tmock := &IndicesExistsService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// IndicesExistsService is an autogenerated mock type for the IndicesExistsService type\ntype IndicesExistsService struct {\n\tmock.Mock\n}\n\ntype IndicesExistsService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *IndicesExistsService) EXPECT() *IndicesExistsService_Expecter {\n\treturn &IndicesExistsService_Expecter{mock: &_m.Mock}\n}\n\n// Do provides a mock function for the type IndicesExistsService\nfunc (_mock *IndicesExistsService) Do(ctx context.Context) (bool, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Do\")\n\t}\n\n\tvar r0 bool\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) (bool, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) bool); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndicesExistsService_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'\ntype IndicesExistsService_Do_Call struct {\n\t*mock.Call\n}\n\n// Do is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *IndicesExistsService_Expecter) Do(ctx interface{}) *IndicesExistsService_Do_Call {\n\treturn &IndicesExistsService_Do_Call{Call: _e.mock.On(\"Do\", ctx)}\n}\n\nfunc (_c *IndicesExistsService_Do_Call) Run(run func(ctx context.Context)) *IndicesExistsService_Do_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndicesExistsService_Do_Call) Return(b bool, err error) *IndicesExistsService_Do_Call {\n\t_c.Call.Return(b, err)\n\treturn _c\n}\n\nfunc (_c *IndicesExistsService_Do_Call) RunAndReturn(run func(ctx context.Context) (bool, error)) *IndicesExistsService_Do_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewIndicesCreateService creates a new instance of IndicesCreateService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIndicesCreateService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *IndicesCreateService {\n\tmock := &IndicesCreateService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// IndicesCreateService is an autogenerated mock type for the IndicesCreateService type\ntype IndicesCreateService struct {\n\tmock.Mock\n}\n\ntype IndicesCreateService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *IndicesCreateService) EXPECT() *IndicesCreateService_Expecter {\n\treturn &IndicesCreateService_Expecter{mock: &_m.Mock}\n}\n\n// Body provides a mock function for the type IndicesCreateService\nfunc (_mock *IndicesCreateService) Body(mapping string) elasticsearch.IndicesCreateService {\n\tret := _mock.Called(mapping)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Body\")\n\t}\n\n\tvar r0 elasticsearch.IndicesCreateService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndicesCreateService); ok {\n\t\tr0 = returnFunc(mapping)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndicesCreateService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// IndicesCreateService_Body_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Body'\ntype IndicesCreateService_Body_Call struct {\n\t*mock.Call\n}\n\n// Body is a helper method to define mock.On call\n//   - mapping string\nfunc (_e *IndicesCreateService_Expecter) Body(mapping interface{}) *IndicesCreateService_Body_Call {\n\treturn &IndicesCreateService_Body_Call{Call: _e.mock.On(\"Body\", mapping)}\n}\n\nfunc (_c *IndicesCreateService_Body_Call) Run(run func(mapping string)) *IndicesCreateService_Body_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndicesCreateService_Body_Call) Return(indicesCreateService elasticsearch.IndicesCreateService) *IndicesCreateService_Body_Call {\n\t_c.Call.Return(indicesCreateService)\n\treturn _c\n}\n\nfunc (_c *IndicesCreateService_Body_Call) RunAndReturn(run func(mapping string) elasticsearch.IndicesCreateService) *IndicesCreateService_Body_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Do provides a mock function for the type IndicesCreateService\nfunc (_mock *IndicesCreateService) Do(ctx context.Context) (*elastic.IndicesCreateResult, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Do\")\n\t}\n\n\tvar r0 *elastic.IndicesCreateResult\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) (*elastic.IndicesCreateResult, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) *elastic.IndicesCreateResult); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*elastic.IndicesCreateResult)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndicesCreateService_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'\ntype IndicesCreateService_Do_Call struct {\n\t*mock.Call\n}\n\n// Do is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *IndicesCreateService_Expecter) Do(ctx interface{}) *IndicesCreateService_Do_Call {\n\treturn &IndicesCreateService_Do_Call{Call: _e.mock.On(\"Do\", ctx)}\n}\n\nfunc (_c *IndicesCreateService_Do_Call) Run(run func(ctx context.Context)) *IndicesCreateService_Do_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndicesCreateService_Do_Call) Return(indicesCreateResult *elastic.IndicesCreateResult, err error) *IndicesCreateService_Do_Call {\n\t_c.Call.Return(indicesCreateResult, err)\n\treturn _c\n}\n\nfunc (_c *IndicesCreateService_Do_Call) RunAndReturn(run func(ctx context.Context) (*elastic.IndicesCreateResult, error)) *IndicesCreateService_Do_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewIndicesDeleteService creates a new instance of IndicesDeleteService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIndicesDeleteService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *IndicesDeleteService {\n\tmock := &IndicesDeleteService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// IndicesDeleteService is an autogenerated mock type for the IndicesDeleteService type\ntype IndicesDeleteService struct {\n\tmock.Mock\n}\n\ntype IndicesDeleteService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *IndicesDeleteService) EXPECT() *IndicesDeleteService_Expecter {\n\treturn &IndicesDeleteService_Expecter{mock: &_m.Mock}\n}\n\n// Do provides a mock function for the type IndicesDeleteService\nfunc (_mock *IndicesDeleteService) Do(ctx context.Context) (*elastic.IndicesDeleteResponse, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Do\")\n\t}\n\n\tvar r0 *elastic.IndicesDeleteResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) (*elastic.IndicesDeleteResponse, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) *elastic.IndicesDeleteResponse); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*elastic.IndicesDeleteResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// IndicesDeleteService_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'\ntype IndicesDeleteService_Do_Call struct {\n\t*mock.Call\n}\n\n// Do is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *IndicesDeleteService_Expecter) Do(ctx interface{}) *IndicesDeleteService_Do_Call {\n\treturn &IndicesDeleteService_Do_Call{Call: _e.mock.On(\"Do\", ctx)}\n}\n\nfunc (_c *IndicesDeleteService_Do_Call) Run(run func(ctx context.Context)) *IndicesDeleteService_Do_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndicesDeleteService_Do_Call) Return(indicesDeleteResponse *elastic.IndicesDeleteResponse, err error) *IndicesDeleteService_Do_Call {\n\t_c.Call.Return(indicesDeleteResponse, err)\n\treturn _c\n}\n\nfunc (_c *IndicesDeleteService_Do_Call) RunAndReturn(run func(ctx context.Context) (*elastic.IndicesDeleteResponse, error)) *IndicesDeleteService_Do_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewTemplateCreateService creates a new instance of TemplateCreateService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewTemplateCreateService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *TemplateCreateService {\n\tmock := &TemplateCreateService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// TemplateCreateService is an autogenerated mock type for the TemplateCreateService type\ntype TemplateCreateService struct {\n\tmock.Mock\n}\n\ntype TemplateCreateService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *TemplateCreateService) EXPECT() *TemplateCreateService_Expecter {\n\treturn &TemplateCreateService_Expecter{mock: &_m.Mock}\n}\n\n// Body provides a mock function for the type TemplateCreateService\nfunc (_mock *TemplateCreateService) Body(mapping string) elasticsearch.TemplateCreateService {\n\tret := _mock.Called(mapping)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Body\")\n\t}\n\n\tvar r0 elasticsearch.TemplateCreateService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.TemplateCreateService); ok {\n\t\tr0 = returnFunc(mapping)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.TemplateCreateService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// TemplateCreateService_Body_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Body'\ntype TemplateCreateService_Body_Call struct {\n\t*mock.Call\n}\n\n// Body is a helper method to define mock.On call\n//   - mapping string\nfunc (_e *TemplateCreateService_Expecter) Body(mapping interface{}) *TemplateCreateService_Body_Call {\n\treturn &TemplateCreateService_Body_Call{Call: _e.mock.On(\"Body\", mapping)}\n}\n\nfunc (_c *TemplateCreateService_Body_Call) Run(run func(mapping string)) *TemplateCreateService_Body_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *TemplateCreateService_Body_Call) Return(templateCreateService elasticsearch.TemplateCreateService) *TemplateCreateService_Body_Call {\n\t_c.Call.Return(templateCreateService)\n\treturn _c\n}\n\nfunc (_c *TemplateCreateService_Body_Call) RunAndReturn(run func(mapping string) elasticsearch.TemplateCreateService) *TemplateCreateService_Body_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Do provides a mock function for the type TemplateCreateService\nfunc (_mock *TemplateCreateService) Do(ctx context.Context) (*elastic.IndicesPutTemplateResponse, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Do\")\n\t}\n\n\tvar r0 *elastic.IndicesPutTemplateResponse\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) (*elastic.IndicesPutTemplateResponse, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) *elastic.IndicesPutTemplateResponse); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*elastic.IndicesPutTemplateResponse)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// TemplateCreateService_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'\ntype TemplateCreateService_Do_Call struct {\n\t*mock.Call\n}\n\n// Do is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *TemplateCreateService_Expecter) Do(ctx interface{}) *TemplateCreateService_Do_Call {\n\treturn &TemplateCreateService_Do_Call{Call: _e.mock.On(\"Do\", ctx)}\n}\n\nfunc (_c *TemplateCreateService_Do_Call) Run(run func(ctx context.Context)) *TemplateCreateService_Do_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *TemplateCreateService_Do_Call) Return(indicesPutTemplateResponse *elastic.IndicesPutTemplateResponse, err error) *TemplateCreateService_Do_Call {\n\t_c.Call.Return(indicesPutTemplateResponse, err)\n\treturn _c\n}\n\nfunc (_c *TemplateCreateService_Do_Call) RunAndReturn(run func(ctx context.Context) (*elastic.IndicesPutTemplateResponse, error)) *TemplateCreateService_Do_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewIndexService creates a new instance of IndexService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewIndexService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *IndexService {\n\tmock := &IndexService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// IndexService is an autogenerated mock type for the IndexService type\ntype IndexService struct {\n\tmock.Mock\n}\n\ntype IndexService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *IndexService) EXPECT() *IndexService_Expecter {\n\treturn &IndexService_Expecter{mock: &_m.Mock}\n}\n\n// Add provides a mock function for the type IndexService\nfunc (_mock *IndexService) Add() {\n\t_mock.Called()\n\treturn\n}\n\n// IndexService_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'\ntype IndexService_Add_Call struct {\n\t*mock.Call\n}\n\n// Add is a helper method to define mock.On call\nfunc (_e *IndexService_Expecter) Add() *IndexService_Add_Call {\n\treturn &IndexService_Add_Call{Call: _e.mock.On(\"Add\")}\n}\n\nfunc (_c *IndexService_Add_Call) Run(run func()) *IndexService_Add_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexService_Add_Call) Return() *IndexService_Add_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *IndexService_Add_Call) RunAndReturn(run func()) *IndexService_Add_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// BodyJson provides a mock function for the type IndexService\nfunc (_mock *IndexService) BodyJson(body any) elasticsearch.IndexService {\n\tret := _mock.Called(body)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for BodyJson\")\n\t}\n\n\tvar r0 elasticsearch.IndexService\n\tif returnFunc, ok := ret.Get(0).(func(any) elasticsearch.IndexService); ok {\n\t\tr0 = returnFunc(body)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndexService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// IndexService_BodyJson_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BodyJson'\ntype IndexService_BodyJson_Call struct {\n\t*mock.Call\n}\n\n// BodyJson is a helper method to define mock.On call\n//   - body any\nfunc (_e *IndexService_Expecter) BodyJson(body interface{}) *IndexService_BodyJson_Call {\n\treturn &IndexService_BodyJson_Call{Call: _e.mock.On(\"BodyJson\", body)}\n}\n\nfunc (_c *IndexService_BodyJson_Call) Run(run func(body any)) *IndexService_BodyJson_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 any\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexService_BodyJson_Call) Return(indexService elasticsearch.IndexService) *IndexService_BodyJson_Call {\n\t_c.Call.Return(indexService)\n\treturn _c\n}\n\nfunc (_c *IndexService_BodyJson_Call) RunAndReturn(run func(body any) elasticsearch.IndexService) *IndexService_BodyJson_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Id provides a mock function for the type IndexService\nfunc (_mock *IndexService) Id(id string) elasticsearch.IndexService {\n\tret := _mock.Called(id)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Id\")\n\t}\n\n\tvar r0 elasticsearch.IndexService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndexService); ok {\n\t\tr0 = returnFunc(id)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndexService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// IndexService_Id_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Id'\ntype IndexService_Id_Call struct {\n\t*mock.Call\n}\n\n// Id is a helper method to define mock.On call\n//   - id string\nfunc (_e *IndexService_Expecter) Id(id interface{}) *IndexService_Id_Call {\n\treturn &IndexService_Id_Call{Call: _e.mock.On(\"Id\", id)}\n}\n\nfunc (_c *IndexService_Id_Call) Run(run func(id string)) *IndexService_Id_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexService_Id_Call) Return(indexService elasticsearch.IndexService) *IndexService_Id_Call {\n\t_c.Call.Return(indexService)\n\treturn _c\n}\n\nfunc (_c *IndexService_Id_Call) RunAndReturn(run func(id string) elasticsearch.IndexService) *IndexService_Id_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Index provides a mock function for the type IndexService\nfunc (_mock *IndexService) Index(index string) elasticsearch.IndexService {\n\tret := _mock.Called(index)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Index\")\n\t}\n\n\tvar r0 elasticsearch.IndexService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndexService); ok {\n\t\tr0 = returnFunc(index)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndexService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// IndexService_Index_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Index'\ntype IndexService_Index_Call struct {\n\t*mock.Call\n}\n\n// Index is a helper method to define mock.On call\n//   - index string\nfunc (_e *IndexService_Expecter) Index(index interface{}) *IndexService_Index_Call {\n\treturn &IndexService_Index_Call{Call: _e.mock.On(\"Index\", index)}\n}\n\nfunc (_c *IndexService_Index_Call) Run(run func(index string)) *IndexService_Index_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexService_Index_Call) Return(indexService elasticsearch.IndexService) *IndexService_Index_Call {\n\t_c.Call.Return(indexService)\n\treturn _c\n}\n\nfunc (_c *IndexService_Index_Call) RunAndReturn(run func(index string) elasticsearch.IndexService) *IndexService_Index_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Type provides a mock function for the type IndexService\nfunc (_mock *IndexService) Type(typ string) elasticsearch.IndexService {\n\tret := _mock.Called(typ)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Type\")\n\t}\n\n\tvar r0 elasticsearch.IndexService\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.IndexService); ok {\n\t\tr0 = returnFunc(typ)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.IndexService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// IndexService_Type_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Type'\ntype IndexService_Type_Call struct {\n\t*mock.Call\n}\n\n// Type is a helper method to define mock.On call\n//   - typ string\nfunc (_e *IndexService_Expecter) Type(typ interface{}) *IndexService_Type_Call {\n\treturn &IndexService_Type_Call{Call: _e.mock.On(\"Type\", typ)}\n}\n\nfunc (_c *IndexService_Type_Call) Run(run func(typ string)) *IndexService_Type_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *IndexService_Type_Call) Return(indexService elasticsearch.IndexService) *IndexService_Type_Call {\n\t_c.Call.Return(indexService)\n\treturn _c\n}\n\nfunc (_c *IndexService_Type_Call) RunAndReturn(run func(typ string) elasticsearch.IndexService) *IndexService_Type_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSearchService creates a new instance of SearchService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSearchService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SearchService {\n\tmock := &SearchService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SearchService is an autogenerated mock type for the SearchService type\ntype SearchService struct {\n\tmock.Mock\n}\n\ntype SearchService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SearchService) EXPECT() *SearchService_Expecter {\n\treturn &SearchService_Expecter{mock: &_m.Mock}\n}\n\n// Aggregation provides a mock function for the type SearchService\nfunc (_mock *SearchService) Aggregation(name string, aggregation elastic.Aggregation) elasticsearch.SearchService {\n\tret := _mock.Called(name, aggregation)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Aggregation\")\n\t}\n\n\tvar r0 elasticsearch.SearchService\n\tif returnFunc, ok := ret.Get(0).(func(string, elastic.Aggregation) elasticsearch.SearchService); ok {\n\t\tr0 = returnFunc(name, aggregation)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.SearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SearchService_Aggregation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Aggregation'\ntype SearchService_Aggregation_Call struct {\n\t*mock.Call\n}\n\n// Aggregation is a helper method to define mock.On call\n//   - name string\n//   - aggregation elastic.Aggregation\nfunc (_e *SearchService_Expecter) Aggregation(name interface{}, aggregation interface{}) *SearchService_Aggregation_Call {\n\treturn &SearchService_Aggregation_Call{Call: _e.mock.On(\"Aggregation\", name, aggregation)}\n}\n\nfunc (_c *SearchService_Aggregation_Call) Run(run func(name string, aggregation elastic.Aggregation)) *SearchService_Aggregation_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\tvar arg1 elastic.Aggregation\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(elastic.Aggregation)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SearchService_Aggregation_Call) Return(searchService elasticsearch.SearchService) *SearchService_Aggregation_Call {\n\t_c.Call.Return(searchService)\n\treturn _c\n}\n\nfunc (_c *SearchService_Aggregation_Call) RunAndReturn(run func(name string, aggregation elastic.Aggregation) elasticsearch.SearchService) *SearchService_Aggregation_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Do provides a mock function for the type SearchService\nfunc (_mock *SearchService) Do(ctx context.Context) (*elastic.SearchResult, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Do\")\n\t}\n\n\tvar r0 *elastic.SearchResult\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) (*elastic.SearchResult, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) *elastic.SearchResult); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*elastic.SearchResult)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SearchService_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'\ntype SearchService_Do_Call struct {\n\t*mock.Call\n}\n\n// Do is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *SearchService_Expecter) Do(ctx interface{}) *SearchService_Do_Call {\n\treturn &SearchService_Do_Call{Call: _e.mock.On(\"Do\", ctx)}\n}\n\nfunc (_c *SearchService_Do_Call) Run(run func(ctx context.Context)) *SearchService_Do_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SearchService_Do_Call) Return(searchResult *elastic.SearchResult, err error) *SearchService_Do_Call {\n\t_c.Call.Return(searchResult, err)\n\treturn _c\n}\n\nfunc (_c *SearchService_Do_Call) RunAndReturn(run func(ctx context.Context) (*elastic.SearchResult, error)) *SearchService_Do_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// IgnoreUnavailable provides a mock function for the type SearchService\nfunc (_mock *SearchService) IgnoreUnavailable(ignoreUnavailable bool) elasticsearch.SearchService {\n\tret := _mock.Called(ignoreUnavailable)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for IgnoreUnavailable\")\n\t}\n\n\tvar r0 elasticsearch.SearchService\n\tif returnFunc, ok := ret.Get(0).(func(bool) elasticsearch.SearchService); ok {\n\t\tr0 = returnFunc(ignoreUnavailable)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.SearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SearchService_IgnoreUnavailable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IgnoreUnavailable'\ntype SearchService_IgnoreUnavailable_Call struct {\n\t*mock.Call\n}\n\n// IgnoreUnavailable is a helper method to define mock.On call\n//   - ignoreUnavailable bool\nfunc (_e *SearchService_Expecter) IgnoreUnavailable(ignoreUnavailable interface{}) *SearchService_IgnoreUnavailable_Call {\n\treturn &SearchService_IgnoreUnavailable_Call{Call: _e.mock.On(\"IgnoreUnavailable\", ignoreUnavailable)}\n}\n\nfunc (_c *SearchService_IgnoreUnavailable_Call) Run(run func(ignoreUnavailable bool)) *SearchService_IgnoreUnavailable_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 bool\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(bool)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SearchService_IgnoreUnavailable_Call) Return(searchService elasticsearch.SearchService) *SearchService_IgnoreUnavailable_Call {\n\t_c.Call.Return(searchService)\n\treturn _c\n}\n\nfunc (_c *SearchService_IgnoreUnavailable_Call) RunAndReturn(run func(ignoreUnavailable bool) elasticsearch.SearchService) *SearchService_IgnoreUnavailable_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Query provides a mock function for the type SearchService\nfunc (_mock *SearchService) Query(query elastic.Query) elasticsearch.SearchService {\n\tret := _mock.Called(query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Query\")\n\t}\n\n\tvar r0 elasticsearch.SearchService\n\tif returnFunc, ok := ret.Get(0).(func(elastic.Query) elasticsearch.SearchService); ok {\n\t\tr0 = returnFunc(query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.SearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SearchService_Query_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Query'\ntype SearchService_Query_Call struct {\n\t*mock.Call\n}\n\n// Query is a helper method to define mock.On call\n//   - query elastic.Query\nfunc (_e *SearchService_Expecter) Query(query interface{}) *SearchService_Query_Call {\n\treturn &SearchService_Query_Call{Call: _e.mock.On(\"Query\", query)}\n}\n\nfunc (_c *SearchService_Query_Call) Run(run func(query elastic.Query)) *SearchService_Query_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 elastic.Query\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(elastic.Query)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SearchService_Query_Call) Return(searchService elasticsearch.SearchService) *SearchService_Query_Call {\n\t_c.Call.Return(searchService)\n\treturn _c\n}\n\nfunc (_c *SearchService_Query_Call) RunAndReturn(run func(query elastic.Query) elasticsearch.SearchService) *SearchService_Query_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Size provides a mock function for the type SearchService\nfunc (_mock *SearchService) Size(size int) elasticsearch.SearchService {\n\tret := _mock.Called(size)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Size\")\n\t}\n\n\tvar r0 elasticsearch.SearchService\n\tif returnFunc, ok := ret.Get(0).(func(int) elasticsearch.SearchService); ok {\n\t\tr0 = returnFunc(size)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.SearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// SearchService_Size_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Size'\ntype SearchService_Size_Call struct {\n\t*mock.Call\n}\n\n// Size is a helper method to define mock.On call\n//   - size int\nfunc (_e *SearchService_Expecter) Size(size interface{}) *SearchService_Size_Call {\n\treturn &SearchService_Size_Call{Call: _e.mock.On(\"Size\", size)}\n}\n\nfunc (_c *SearchService_Size_Call) Run(run func(size int)) *SearchService_Size_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 int\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(int)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SearchService_Size_Call) Return(searchService elasticsearch.SearchService) *SearchService_Size_Call {\n\t_c.Call.Return(searchService)\n\treturn _c\n}\n\nfunc (_c *SearchService_Size_Call) RunAndReturn(run func(size int) elasticsearch.SearchService) *SearchService_Size_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewMultiSearchService creates a new instance of MultiSearchService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewMultiSearchService(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *MultiSearchService {\n\tmock := &MultiSearchService{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// MultiSearchService is an autogenerated mock type for the MultiSearchService type\ntype MultiSearchService struct {\n\tmock.Mock\n}\n\ntype MultiSearchService_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *MultiSearchService) EXPECT() *MultiSearchService_Expecter {\n\treturn &MultiSearchService_Expecter{mock: &_m.Mock}\n}\n\n// Add provides a mock function for the type MultiSearchService\nfunc (_mock *MultiSearchService) Add(requests ...*elastic.SearchRequest) elasticsearch.MultiSearchService {\n\tvar tmpRet mock.Arguments\n\tif len(requests) > 0 {\n\t\ttmpRet = _mock.Called(requests)\n\t} else {\n\t\ttmpRet = _mock.Called()\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Add\")\n\t}\n\n\tvar r0 elasticsearch.MultiSearchService\n\tif returnFunc, ok := ret.Get(0).(func(...*elastic.SearchRequest) elasticsearch.MultiSearchService); ok {\n\t\tr0 = returnFunc(requests...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.MultiSearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// MultiSearchService_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'\ntype MultiSearchService_Add_Call struct {\n\t*mock.Call\n}\n\n// Add is a helper method to define mock.On call\n//   - requests ...*elastic.SearchRequest\nfunc (_e *MultiSearchService_Expecter) Add(requests ...interface{}) *MultiSearchService_Add_Call {\n\treturn &MultiSearchService_Add_Call{Call: _e.mock.On(\"Add\",\n\t\tappend([]interface{}{}, requests...)...)}\n}\n\nfunc (_c *MultiSearchService_Add_Call) Run(run func(requests ...*elastic.SearchRequest)) *MultiSearchService_Add_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []*elastic.SearchRequest\n\t\tvar variadicArgs []*elastic.SearchRequest\n\t\tif len(args) > 0 {\n\t\t\tvariadicArgs = args[0].([]*elastic.SearchRequest)\n\t\t}\n\t\targ0 = variadicArgs\n\t\trun(\n\t\t\targ0...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *MultiSearchService_Add_Call) Return(multiSearchService elasticsearch.MultiSearchService) *MultiSearchService_Add_Call {\n\t_c.Call.Return(multiSearchService)\n\treturn _c\n}\n\nfunc (_c *MultiSearchService_Add_Call) RunAndReturn(run func(requests ...*elastic.SearchRequest) elasticsearch.MultiSearchService) *MultiSearchService_Add_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Do provides a mock function for the type MultiSearchService\nfunc (_mock *MultiSearchService) Do(ctx context.Context) (*elastic.MultiSearchResult, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Do\")\n\t}\n\n\tvar r0 *elastic.MultiSearchResult\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) (*elastic.MultiSearchResult, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) *elastic.MultiSearchResult); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*elastic.MultiSearchResult)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// MultiSearchService_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'\ntype MultiSearchService_Do_Call struct {\n\t*mock.Call\n}\n\n// Do is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *MultiSearchService_Expecter) Do(ctx interface{}) *MultiSearchService_Do_Call {\n\treturn &MultiSearchService_Do_Call{Call: _e.mock.On(\"Do\", ctx)}\n}\n\nfunc (_c *MultiSearchService_Do_Call) Run(run func(ctx context.Context)) *MultiSearchService_Do_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *MultiSearchService_Do_Call) Return(multiSearchResult *elastic.MultiSearchResult, err error) *MultiSearchService_Do_Call {\n\t_c.Call.Return(multiSearchResult, err)\n\treturn _c\n}\n\nfunc (_c *MultiSearchService_Do_Call) RunAndReturn(run func(ctx context.Context) (*elastic.MultiSearchResult, error)) *MultiSearchService_Do_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Index provides a mock function for the type MultiSearchService\nfunc (_mock *MultiSearchService) Index(indices ...string) elasticsearch.MultiSearchService {\n\tvar tmpRet mock.Arguments\n\tif len(indices) > 0 {\n\t\ttmpRet = _mock.Called(indices)\n\t} else {\n\t\ttmpRet = _mock.Called()\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Index\")\n\t}\n\n\tvar r0 elasticsearch.MultiSearchService\n\tif returnFunc, ok := ret.Get(0).(func(...string) elasticsearch.MultiSearchService); ok {\n\t\tr0 = returnFunc(indices...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.MultiSearchService)\n\t\t}\n\t}\n\treturn r0\n}\n\n// MultiSearchService_Index_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Index'\ntype MultiSearchService_Index_Call struct {\n\t*mock.Call\n}\n\n// Index is a helper method to define mock.On call\n//   - indices ...string\nfunc (_e *MultiSearchService_Expecter) Index(indices ...interface{}) *MultiSearchService_Index_Call {\n\treturn &MultiSearchService_Index_Call{Call: _e.mock.On(\"Index\",\n\t\tappend([]interface{}{}, indices...)...)}\n}\n\nfunc (_c *MultiSearchService_Index_Call) Run(run func(indices ...string)) *MultiSearchService_Index_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []string\n\t\tvar variadicArgs []string\n\t\tif len(args) > 0 {\n\t\t\tvariadicArgs = args[0].([]string)\n\t\t}\n\t\targ0 = variadicArgs\n\t\trun(\n\t\t\targ0...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *MultiSearchService_Index_Call) Return(multiSearchService elasticsearch.MultiSearchService) *MultiSearchService_Index_Call {\n\t_c.Call.Return(multiSearchService)\n\treturn _c\n}\n\nfunc (_c *MultiSearchService_Index_Call) RunAndReturn(run func(indices ...string) elasticsearch.MultiSearchService) *MultiSearchService_Index_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewTemplateApplier creates a new instance of TemplateApplier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewTemplateApplier(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *TemplateApplier {\n\tmock := &TemplateApplier{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// TemplateApplier is an autogenerated mock type for the TemplateApplier type\ntype TemplateApplier struct {\n\tmock.Mock\n}\n\ntype TemplateApplier_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *TemplateApplier) EXPECT() *TemplateApplier_Expecter {\n\treturn &TemplateApplier_Expecter{mock: &_m.Mock}\n}\n\n// Execute provides a mock function for the type TemplateApplier\nfunc (_mock *TemplateApplier) Execute(wr io.Writer, data any) error {\n\tret := _mock.Called(wr, data)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Execute\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(io.Writer, any) error); ok {\n\t\tr0 = returnFunc(wr, data)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// TemplateApplier_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'\ntype TemplateApplier_Execute_Call struct {\n\t*mock.Call\n}\n\n// Execute is a helper method to define mock.On call\n//   - wr io.Writer\n//   - data any\nfunc (_e *TemplateApplier_Expecter) Execute(wr interface{}, data interface{}) *TemplateApplier_Execute_Call {\n\treturn &TemplateApplier_Execute_Call{Call: _e.mock.On(\"Execute\", wr, data)}\n}\n\nfunc (_c *TemplateApplier_Execute_Call) Run(run func(wr io.Writer, data any)) *TemplateApplier_Execute_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 io.Writer\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(io.Writer)\n\t\t}\n\t\tvar arg1 any\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(any)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *TemplateApplier_Execute_Call) Return(err error) *TemplateApplier_Execute_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *TemplateApplier_Execute_Call) RunAndReturn(run func(wr io.Writer, data any) error) *TemplateApplier_Execute_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewTemplateBuilder creates a new instance of TemplateBuilder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewTemplateBuilder(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *TemplateBuilder {\n\tmock := &TemplateBuilder{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// TemplateBuilder is an autogenerated mock type for the TemplateBuilder type\ntype TemplateBuilder struct {\n\tmock.Mock\n}\n\ntype TemplateBuilder_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *TemplateBuilder) EXPECT() *TemplateBuilder_Expecter {\n\treturn &TemplateBuilder_Expecter{mock: &_m.Mock}\n}\n\n// Parse provides a mock function for the type TemplateBuilder\nfunc (_mock *TemplateBuilder) Parse(text string) (elasticsearch.TemplateApplier, error) {\n\tret := _mock.Called(text)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Parse\")\n\t}\n\n\tvar r0 elasticsearch.TemplateApplier\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(string) (elasticsearch.TemplateApplier, error)); ok {\n\t\treturn returnFunc(text)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(string) elasticsearch.TemplateApplier); ok {\n\t\tr0 = returnFunc(text)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(elasticsearch.TemplateApplier)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(string) error); ok {\n\t\tr1 = returnFunc(text)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// TemplateBuilder_Parse_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Parse'\ntype TemplateBuilder_Parse_Call struct {\n\t*mock.Call\n}\n\n// Parse is a helper method to define mock.On call\n//   - text string\nfunc (_e *TemplateBuilder_Expecter) Parse(text interface{}) *TemplateBuilder_Parse_Call {\n\treturn &TemplateBuilder_Parse_Call{Call: _e.mock.On(\"Parse\", text)}\n}\n\nfunc (_c *TemplateBuilder_Parse_Call) Run(run func(text string)) *TemplateBuilder_Parse_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *TemplateBuilder_Parse_Call) Return(templateApplier elasticsearch.TemplateApplier, err error) *TemplateBuilder_Parse_Call {\n\t_c.Call.Return(templateApplier, err)\n\treturn _c\n}\n\nfunc (_c *TemplateBuilder_Parse_Call) RunAndReturn(run func(text string) (elasticsearch.TemplateApplier, error)) *TemplateBuilder_Parse_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/query/range_query.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage query\n\n// Package query provides an Elasticsearch RangeQuery implementation.\n// This RangeQuery behaves the same as the Go Elasticsearch client (olivere/elastic),\n// but is rewritten to be compatible with Elasticsearch v9 and avoids deprecated parameters.\n//\n// Deprecated parameters like include_lower, include_upper, from, and to are excluded deliberately.\n\ntype RangeQuery struct {\n\tname      string\n\tqueryName string\n\tparams    map[string]any\n}\n\n// NewRangeQuery creates and initializes a new RangeQuery.\nfunc NewRangeQuery(name string) *RangeQuery {\n\treturn &RangeQuery{\n\t\tname:   name,\n\t\tparams: make(map[string]any),\n\t}\n}\n\n// Generic setter\nfunc (q *RangeQuery) set(key string, val any) *RangeQuery {\n\tq.params[key] = val\n\treturn q\n}\n\nfunc (q *RangeQuery) Gt(val any) *RangeQuery      { return q.set(\"gt\", val) }\nfunc (q *RangeQuery) Gte(val any) *RangeQuery     { return q.set(\"gte\", val) }\nfunc (q *RangeQuery) Lt(val any) *RangeQuery      { return q.set(\"lt\", val) }\nfunc (q *RangeQuery) Lte(val any) *RangeQuery     { return q.set(\"lte\", val) }\nfunc (q *RangeQuery) Boost(b float64) *RangeQuery { return q.set(\"boost\", b) }\nfunc (q *RangeQuery) TimeZone(tz string) *RangeQuery {\n\treturn q.set(\"time_zone\", tz)\n}\n\nfunc (q *RangeQuery) Format(fmt string) *RangeQuery {\n\treturn q.set(\"format\", fmt)\n}\n\nfunc (q *RangeQuery) Relation(r string) *RangeQuery {\n\treturn q.set(\"relation\", r)\n}\n\nfunc (q *RangeQuery) QueryName(queryName string) *RangeQuery {\n\tq.queryName = queryName\n\treturn q\n}\n\n// Source builds and returns the Elasticsearch-compatible representation of the range query.\n\nfunc (q *RangeQuery) Source() (any, error) {\n\tsource := make(map[string]any)\n\trangeQ := make(map[string]any)\n\tsource[\"range\"] = rangeQ\n\trangeQ[q.name] = q.params\n\n\tif q.queryName != \"\" {\n\t\trangeQ[\"_name\"] = q.queryName\n\t}\n\treturn source, nil\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/query/range_query_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage query\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc assertRangeQuery(t *testing.T, q *RangeQuery, expected string) {\n\tt.Helper()\n\tsrc, err := q.Source()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdata, err := json.Marshal(src)\n\tif err != nil {\n\t\tt.Fatalf(\"marshaling to JSON failed: %v\", err)\n\t}\n\tgot := string(data)\n\tif got != expected {\n\t\tt.Errorf(\"expected:\\n%s\\ngot:\\n%s\", expected, got)\n\t}\n}\n\nfunc TestRangeQuery(t *testing.T) {\n\tq := NewRangeQuery(\"postDate\").\n\t\tGte(\"2010-03-01\").\n\t\tLte(\"2010-04-01\").\n\t\tBoost(3).\n\t\tRelation(\"within\").\n\t\tQueryName(\"my_query\")\n\n\texpected := `{\"range\":{\"_name\":\"my_query\",\"postDate\":{\"boost\":3,\"gte\":\"2010-03-01\",\"lte\":\"2010-04-01\",\"relation\":\"within\"}}}`\n\tassertRangeQuery(t, q, expected)\n}\n\nfunc TestRangeQueryWithTimeZone(t *testing.T) {\n\tq := NewRangeQuery(\"born\").\n\t\tGte(\"2012-01-01\").\n\t\tLte(\"now\").\n\t\tTimeZone(\"+1:00\")\n\n\texpected := `{\"range\":{\"born\":{\"gte\":\"2012-01-01\",\"lte\":\"now\",\"time_zone\":\"+1:00\"}}}`\n\tassertRangeQuery(t, q, expected)\n}\n\nfunc TestRangeQueryWithFormat(t *testing.T) {\n\tq := NewRangeQuery(\"born\").\n\t\tGt(\"2012/01/01\").\n\t\tLt(\"now\").\n\t\tFormat(\"yyyy/MM/dd\")\n\n\texpected := `{\"range\":{\"born\":{\"format\":\"yyyy/MM/dd\",\"gt\":\"2012/01/01\",\"lt\":\"now\"}}}`\n\tassertRangeQuery(t, q, expected)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/textTemplate.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"io\"\n\t\"text/template\"\n)\n\n// TemplateApplier applies a parsed template to input data that maps to the template's variables.\ntype TemplateApplier interface {\n\tExecute(wr io.Writer, data any) error\n}\n\n// TemplateBuilder parses a given string and returns TemplateApplier\n// TemplateBuilder is an abstraction to support mocking template/text\ntype TemplateBuilder interface {\n\tParse(text string) (TemplateApplier, error)\n}\n\n// TextTemplateBuilder implements TemplateBuilder\ntype TextTemplateBuilder struct{}\n\n// Parse is a wrapper for template.Parse\nfunc (TextTemplateBuilder) Parse(tmpl string) (TemplateApplier, error) {\n\treturn template.New(\"mapping\").Parse(tmpl)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/textTemplate_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParse(t *testing.T) {\n\tconst wantString = \"text/template parse\"\n\tvalues := struct {\n\t\tStr string\n\t}{wantString}\n\n\ttemplate := \"Parse is a wrapper for {{ .Str }} function.\"\n\twriter := new(bytes.Buffer)\n\ttestTemplateBuilder := TextTemplateBuilder{}\n\tparsedStr, err := testTemplateBuilder.Parse(template)\n\trequire.NoError(t, err)\n\terr = parsedStr.Execute(writer, values)\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"Parse is a wrapper for text/template parse function.\", writer.String())\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/wrapper/empty_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage eswrapper\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/wrapper/wrapper.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage eswrapper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tesv8 \"github.com/elastic/go-elasticsearch/v9\"\n\tesv8api \"github.com/elastic/go-elasticsearch/v9/esapi\"\n\t\"github.com/olivere/elastic/v7\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n)\n\n// This file avoids lint because the Id and Json are required to be capitalized, but must match an outside library.\n\n// ClientWrapper is a wrapper around elastic.Client\ntype ClientWrapper struct {\n\tclient      *elastic.Client\n\tbulkService *elastic.BulkProcessor\n\tesVersion   uint\n\tclientV8    *esv8.Client\n}\n\n// GetVersion returns the ElasticSearch Version\nfunc (c ClientWrapper) GetVersion() uint {\n\treturn c.esVersion\n}\n\n// WrapESClient creates a ESClient out of *elastic.Client.\nfunc WrapESClient(client *elastic.Client, s *elastic.BulkProcessor, esVersion uint, clientV8 *esv8.Client) ClientWrapper {\n\treturn ClientWrapper{\n\t\tclient:      client,\n\t\tbulkService: s,\n\t\tesVersion:   esVersion,\n\t\tclientV8:    clientV8,\n\t}\n}\n\n// IndexExists calls this function to internal client.\nfunc (c ClientWrapper) IndexExists(index string) es.IndicesExistsService {\n\treturn WrapESIndicesExistsService(c.client.IndexExists(index))\n}\n\n// CreateIndex calls this function to internal client.\nfunc (c ClientWrapper) CreateIndex(index string) es.IndicesCreateService {\n\treturn WrapESIndicesCreateService(c.client.CreateIndex(index))\n}\n\n// DeleteIndex calls this function to internal client.\nfunc (c ClientWrapper) DeleteIndex(index string) es.IndicesDeleteService {\n\treturn WrapESIndicesDeleteService(c.client.DeleteIndex(index))\n}\n\n// CreateTemplate calls this function to internal client.\nfunc (c ClientWrapper) CreateTemplate(ttype string) es.TemplateCreateService {\n\tif c.esVersion >= 8 {\n\t\treturn TemplateCreatorWrapperV8{\n\t\t\tindicesV8:    c.clientV8.Indices,\n\t\t\ttemplateName: ttype,\n\t\t}\n\t}\n\treturn WrapESTemplateCreateService(c.client.IndexPutTemplate(ttype))\n}\n\n// Index calls this function to internal client.\nfunc (c ClientWrapper) Index() es.IndexService {\n\tr := elastic.NewBulkIndexRequest()\n\treturn WrapESIndexService(r, c.bulkService, c.esVersion)\n}\n\n// Search calls this function to internal client.\nfunc (c ClientWrapper) Search(indices ...string) es.SearchService {\n\tsearchService := c.client.Search(indices...)\n\tif c.esVersion >= 7 {\n\t\tsearchService = searchService.RestTotalHitsAsInt(true)\n\t}\n\treturn WrapESSearchService(searchService)\n}\n\n// MultiSearch calls this function to internal client.\nfunc (c ClientWrapper) MultiSearch() es.MultiSearchService {\n\tmultiSearchService := c.client.MultiSearch()\n\treturn WrapESMultiSearchService(multiSearchService)\n}\n\n// Close closes ESClient and flushes all data to the storage.\nfunc (c ClientWrapper) Close() error {\n\tc.client.Stop()\n\treturn c.bulkService.Close()\n}\n\n// ---\n\n// IndicesExistsServiceWrapper is a wrapper around elastic.IndicesExistsService\ntype IndicesExistsServiceWrapper struct {\n\tindicesExistsService *elastic.IndicesExistsService\n}\n\n// WrapESIndicesExistsService creates an ESIndicesExistsService out of *elastic.IndicesExistsService.\nfunc WrapESIndicesExistsService(indicesExistsService *elastic.IndicesExistsService) IndicesExistsServiceWrapper {\n\treturn IndicesExistsServiceWrapper{indicesExistsService: indicesExistsService}\n}\n\n// Do calls this function to internal service.\nfunc (e IndicesExistsServiceWrapper) Do(ctx context.Context) (bool, error) {\n\treturn e.indicesExistsService.Do(ctx)\n}\n\n// ---\n\n// IndicesCreateServiceWrapper is a wrapper around elastic.IndicesCreateService\ntype IndicesCreateServiceWrapper struct {\n\tindicesCreateService *elastic.IndicesCreateService\n}\n\n// WrapESIndicesCreateService creates an ESIndicesCreateService out of *elastic.IndicesCreateService.\nfunc WrapESIndicesCreateService(indicesCreateService *elastic.IndicesCreateService) IndicesCreateServiceWrapper {\n\treturn IndicesCreateServiceWrapper{indicesCreateService: indicesCreateService}\n}\n\n// Body calls this function to internal service.\nfunc (c IndicesCreateServiceWrapper) Body(mapping string) es.IndicesCreateService {\n\treturn WrapESIndicesCreateService(c.indicesCreateService.Body(mapping))\n}\n\n// Do calls this function to internal service.\nfunc (c IndicesCreateServiceWrapper) Do(ctx context.Context) (*elastic.IndicesCreateResult, error) {\n\treturn c.indicesCreateService.Do(ctx)\n}\n\n// TemplateCreateServiceWrapper is a wrapper around elastic.IndicesPutTemplateService.\ntype TemplateCreateServiceWrapper struct {\n\tmappingCreateService *elastic.IndicesPutTemplateService\n}\n\n// IndicesDeleteServiceWrapper is a wrapper around elastic.IndicesDeleteService\ntype IndicesDeleteServiceWrapper struct {\n\tindicesDeleteService *elastic.IndicesDeleteService\n}\n\n// WrapESIndicesDeleteService creates an ESIndicesDeleteService out of *elastic.IndicesDeleteService.\nfunc WrapESIndicesDeleteService(indicesDeleteService *elastic.IndicesDeleteService) IndicesDeleteServiceWrapper {\n\treturn IndicesDeleteServiceWrapper{indicesDeleteService: indicesDeleteService}\n}\n\n// Do calls this function to internal service.\nfunc (e IndicesDeleteServiceWrapper) Do(ctx context.Context) (*elastic.IndicesDeleteResponse, error) {\n\treturn e.indicesDeleteService.Do(ctx)\n}\n\n// WrapESTemplateCreateService creates an TemplateCreateService out of *elastic.IndicesPutTemplateService.\nfunc WrapESTemplateCreateService(mappingCreateService *elastic.IndicesPutTemplateService) TemplateCreateServiceWrapper {\n\treturn TemplateCreateServiceWrapper{mappingCreateService: mappingCreateService}\n}\n\n// Body calls this function to internal service.\nfunc (c TemplateCreateServiceWrapper) Body(mapping string) es.TemplateCreateService {\n\treturn WrapESTemplateCreateService(c.mappingCreateService.BodyString(mapping))\n}\n\n// Do calls this function to internal service.\nfunc (c TemplateCreateServiceWrapper) Do(ctx context.Context) (*elastic.IndicesPutTemplateResponse, error) {\n\treturn c.mappingCreateService.Do(ctx)\n}\n\n// ---\n\n// TemplateCreatorWrapperV8 implements es.TemplateCreateService.\ntype TemplateCreatorWrapperV8 struct {\n\tindicesV8       *esv8api.Indices\n\ttemplateName    string\n\ttemplateMapping string\n}\n\n// Body adds mapping to the future request.\nfunc (c TemplateCreatorWrapperV8) Body(mapping string) es.TemplateCreateService {\n\tcc := c // clone\n\tcc.templateMapping = mapping\n\treturn cc\n}\n\n// Do executes Put Template command.\nfunc (c TemplateCreatorWrapperV8) Do(context.Context) (*elastic.IndicesPutTemplateResponse, error) {\n\tresp, err := c.indicesV8.PutIndexTemplate(c.templateName, strings.NewReader(c.templateMapping))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating index template %s: %w\", c.templateName, err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"error creating index template %s: %s\", c.templateName, resp)\n\t}\n\treturn nil, nil // no response expected by span writer\n}\n\n// ---\n\n// IndexServiceWrapper is a wrapper around elastic.ESIndexService.\n// See wrapper_nolint.go for more functions.\ntype IndexServiceWrapper struct {\n\tbulkIndexReq *elastic.BulkIndexRequest\n\tbulkService  *elastic.BulkProcessor\n\tesVersion    uint\n}\n\n// WrapESIndexService creates an ESIndexService out of *elastic.ESIndexService.\nfunc WrapESIndexService(indexService *elastic.BulkIndexRequest, bulkService *elastic.BulkProcessor, esVersion uint) IndexServiceWrapper {\n\treturn IndexServiceWrapper{bulkIndexReq: indexService, bulkService: bulkService, esVersion: esVersion}\n}\n\n// Index calls this function to internal service.\nfunc (i IndexServiceWrapper) Index(index string) es.IndexService {\n\treturn WrapESIndexService(i.bulkIndexReq.Index(index), i.bulkService, i.esVersion)\n}\n\n// Type calls this function to internal service.\nfunc (i IndexServiceWrapper) Type(typ string) es.IndexService {\n\tif i.esVersion >= 7 {\n\t\treturn WrapESIndexService(i.bulkIndexReq, i.bulkService, i.esVersion)\n\t}\n\treturn WrapESIndexService(i.bulkIndexReq.Type(typ), i.bulkService, i.esVersion)\n}\n\n// Add adds the request to bulk service\nfunc (i IndexServiceWrapper) Add() {\n\ti.bulkService.Add(i.bulkIndexReq)\n}\n\n// ---\n\n// SearchServiceWrapper is a wrapper around elastic.ESSearchService\ntype SearchServiceWrapper struct {\n\tsearchService *elastic.SearchService\n}\n\n// WrapESSearchService creates an ESSearchService out of *elastic.ESSearchService.\nfunc WrapESSearchService(searchService *elastic.SearchService) SearchServiceWrapper {\n\treturn SearchServiceWrapper{searchService: searchService}\n}\n\n// Size calls this function to internal service.\nfunc (s SearchServiceWrapper) Size(size int) es.SearchService {\n\treturn WrapESSearchService(s.searchService.Size(size))\n}\n\n// Aggregation calls this function to internal service.\nfunc (s SearchServiceWrapper) Aggregation(name string, aggregation elastic.Aggregation) es.SearchService {\n\treturn WrapESSearchService(s.searchService.Aggregation(name, aggregation))\n}\n\n// IgnoreUnavailable calls this function to internal service.\nfunc (s SearchServiceWrapper) IgnoreUnavailable(ignoreUnavailable bool) es.SearchService {\n\treturn WrapESSearchService(s.searchService.IgnoreUnavailable(ignoreUnavailable))\n}\n\n// Query calls this function to internal service.\nfunc (s SearchServiceWrapper) Query(query elastic.Query) es.SearchService {\n\treturn WrapESSearchService(s.searchService.Query(query))\n}\n\n// Do calls this function to internal service.\nfunc (s SearchServiceWrapper) Do(ctx context.Context) (*elastic.SearchResult, error) {\n\treturn s.searchService.Do(ctx)\n}\n\n// MultiSearchServiceWrapper is a wrapper around elastic.ESMultiSearchService\ntype MultiSearchServiceWrapper struct {\n\tmultiSearchService *elastic.MultiSearchService\n}\n\n// WrapESMultiSearchService creates an ESSearchService out of *elastic.ESSearchService.\nfunc WrapESMultiSearchService(multiSearchService *elastic.MultiSearchService) MultiSearchServiceWrapper {\n\treturn MultiSearchServiceWrapper{multiSearchService: multiSearchService}\n}\n\n// Add calls this function to internal service.\nfunc (s MultiSearchServiceWrapper) Add(requests ...*elastic.SearchRequest) es.MultiSearchService {\n\treturn WrapESMultiSearchService(s.multiSearchService.Add(requests...))\n}\n\n// Index calls this function to internal service.\nfunc (s MultiSearchServiceWrapper) Index(indices ...string) es.MultiSearchService {\n\treturn WrapESMultiSearchService(s.multiSearchService.Index(indices...))\n}\n\n// Do calls this function to internal service.\nfunc (s MultiSearchServiceWrapper) Do(ctx context.Context) (*elastic.MultiSearchResult, error) {\n\treturn s.multiSearchService.Do(ctx)\n}\n"
  },
  {
    "path": "internal/storage/elasticsearch/wrapper/wrapper_nolint.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage eswrapper\n\nimport es \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\n// Some of the functions of elastic.BulkIndexRequest violate golint rules,\n// e.g. Id() should be ID() and BodyJson() should be BodyJSON().\n\n// Id calls this function to internal service.\nfunc (i IndexServiceWrapper) Id(id string) es.IndexService {\n\treturn WrapESIndexService(i.bulkIndexReq.Id(id), i.bulkService, i.esVersion)\n}\n\n// BodyJson calls this function to internal service.\nfunc (i IndexServiceWrapper) BodyJson(body any) es.IndexService {\n\treturn WrapESIndexService(i.bulkIndexReq.Doc(body), i.bulkService, i.esVersion)\n}\n"
  },
  {
    "path": "internal/storage/integration/badgerstore_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\n\tv1badger \"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype BadgerIntegrationStorage struct {\n\tStorageIntegration\n\tfactory *badger.Factory\n}\n\nfunc (s *BadgerIntegrationStorage) initialize(t *testing.T) {\n\tcfg := v1badger.DefaultConfig()\n\tcfg.Ephemeral = false\n\tvar err error\n\ttelset := telemetry.NoopSettings()\n\ttelset.Logger = zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller()))\n\ts.factory, err = badger.NewFactory(*cfg, telset)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\ts.factory.Close()\n\t})\n\n\ts.TraceWriter, err = s.factory.CreateTraceWriter()\n\trequire.NoError(t, err)\n\n\ts.TraceReader, err = s.factory.CreateTraceReader()\n\trequire.NoError(t, err)\n\n\ts.SamplingStore, err = s.factory.CreateSamplingStore(0)\n\trequire.NoError(t, err)\n}\n\nfunc (s *BadgerIntegrationStorage) cleanUp(t *testing.T) {\n\trequire.NoError(t, s.factory.Purge(context.Background()))\n}\n\nfunc TestBadgerStorage(t *testing.T) {\n\tSkipUnlessEnv(t, \"badger\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnce(t)\n\t})\n\ts := &BadgerIntegrationStorage{\n\t\tStorageIntegration: StorageIntegration{\n\t\t\t// TODO: remove this badger supports returning spanKind from GetOperations\n\t\t\tGetOperationsMissingSpanKind: true,\n\t\t},\n\t}\n\ts.CleanUp = s.cleanUp\n\ts.initialize(t)\n\ts.RunAll(t)\n}\n"
  },
  {
    "path": "internal/storage/integration/cassandra_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2019 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\n\tcasconfig \"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\tcassandrav1 \"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/v1adapter\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype CassandraStorageIntegration struct {\n\tStorageIntegration\n\tfactory *cassandra.Factory\n}\n\nfunc newCassandraStorageIntegration() *CassandraStorageIntegration {\n\ts := &CassandraStorageIntegration{\n\t\tStorageIntegration: StorageIntegration{\n\t\t\tGetDependenciesReturnsSource: true,\n\n\t\t\tSkipList: CassandraSkippedTests,\n\t\t},\n\t}\n\ts.CleanUp = s.cleanUp\n\treturn s\n}\n\nfunc (s *CassandraStorageIntegration) cleanUp(t *testing.T) {\n\trequire.NoError(t, s.factory.Purge(context.Background()))\n}\n\nfunc (s *CassandraStorageIntegration) initializeCassandra(t *testing.T) {\n\tusername := os.Getenv(\"CASSANDRA_USERNAME\")\n\tpassword := os.Getenv(\"CASSANDRA_PASSWORD\")\n\tcfg := casconfig.Configuration{\n\t\tSchema: casconfig.Schema{\n\t\t\tKeyspace: \"jaeger_v1_dc1\",\n\t\t},\n\t\tConnection: casconfig.Connection{\n\t\t\tServers: []string{\"127.0.0.1\"},\n\t\t\tAuthenticator: casconfig.Authenticator{\n\t\t\t\tBasic: casconfig.BasicAuthenticator{\n\t\t\t\t\tUsername:              username,\n\t\t\t\t\tPassword:              password,\n\t\t\t\t\tAllowedAuthenticators: []string{\"org.apache.cassandra.auth.PasswordAuthenticator\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tTLS: configtls.ClientConfig{\n\t\t\t\tInsecure: true,\n\t\t\t},\n\t\t},\n\t}\n\tdefCfg := casconfig.DefaultConfiguration()\n\tcfg.ApplyDefaults(&defCfg)\n\topts := cassandrav1.Options{\n\t\tConfiguration: cfg,\n\t\tIndex: cassandrav1.IndexConfig{\n\t\t\tLogs:        true,\n\t\t\tTags:        true,\n\t\t\tProcessTags: true,\n\t\t},\n\t\tSpanStoreWriteCacheTTL: time.Hour * 12,\n\t\tArchiveEnabled:         false,\n\t}\n\tf, err := cassandra.NewFactory(opts, telemetry.NoopSettings())\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\trequire.NoError(t, f.Close())\n\t})\n\ts.factory = f\n\ts.TraceWriter, err = f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\ts.TraceReader, err = f.CreateTraceReader()\n\trequire.NoError(t, err)\n\ts.SamplingStore, err = f.CreateSamplingStore(0)\n\trequire.NoError(t, err)\n\ts.initializeDependencyReaderAndWriter(t, f)\n}\n\nfunc (s *CassandraStorageIntegration) initializeDependencyReaderAndWriter(t *testing.T, f *cassandra.Factory) {\n\tvar err error\n\tdependencyReader, err := f.CreateDependencyReader()\n\trequire.NoError(t, err)\n\ts.DependencyReader = dependencyReader\n\n\t// TODO: Update this when the factory interface has CreateDependencyWriter\n\tif dependencyWriter, ok := dependencyReader.(dependencystore.Writer); !ok {\n\t\tt.Log(\"DependencyWriter not implemented \")\n\t} else {\n\t\ts.DependencyWriter = v1adapter.NewDependencyWriter(dependencyWriter)\n\t}\n}\n\nfunc TestCassandraStorage(t *testing.T) {\n\tSkipUnlessEnv(t, \"cassandra\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnce(t)\n\t})\n\ts := newCassandraStorageIntegration()\n\ts.initializeCassandra(t)\n\ts.RunAll(t)\n}\n"
  },
  {
    "path": "internal/storage/integration/dates.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n)\n\n// dateOffsetNormalizer normalizes timestamps by replacing their date\n// (year, month, day) with a fixed date computed using a day offset\n// from the current UTC date, while preserving the original time.\n// This is required in integration tests because the fixtures have\n// hardcoded start time and other timestamps and we need to make them\n// recent to fetch from the reader. Timestamps whose original UTC date\n// is 2017-01-25 use a -2 day offset; all other timestamps use a -1 day\n// offset. These offsets are selected to keep the upgrade from v1 to v2 consistent.\ntype dateOffsetNormalizer struct {\n\ttm time.Time\n}\n\nfunc newDateOffsetNormalizer(tm time.Time) dateOffsetNormalizer {\n\treturn dateOffsetNormalizer{tm: tm}\n}\n\nfunc (d dateOffsetNormalizer) normalizeTrace(td ptrace.Traces) {\n\tfor _, span := range jptrace.SpanIter(td) {\n\t\tspan.SetStartTimestamp(d.normalizeTime(span.StartTimestamp()))\n\t\tspan.SetEndTimestamp(d.normalizeTime(span.EndTimestamp()))\n\t\tfor _, event := range span.Events().All() {\n\t\t\tevent.SetTimestamp(d.normalizeTime(event.Timestamp()))\n\t\t}\n\t}\n}\n\nfunc (d dateOffsetNormalizer) normalizeTime(t pcommon.Timestamp) pcommon.Timestamp {\n\ttm := t.AsTime().UTC()\n\toffset := -1\n\t// Apply a -2 day offset for any timestamp whose UTC date is 2017-01-25.\n\t// All other timestamps use the default -1 day offset to preserve existing behavior.\n\tyearOrig, monthOrig, dayOrig := tm.Date()\n\tif yearOrig == 2017 && monthOrig == time.January && dayOrig == 25 {\n\t\toffset = -2\n\t}\n\tyear, month, day := d.tm.UTC().AddDate(0, 0, offset).Date()\n\tnewTm := time.Date(\n\t\tyear,\n\t\tmonth,\n\t\tday,\n\t\ttm.Hour(),\n\t\ttm.Minute(),\n\t\ttm.Second(),\n\t\ttm.Nanosecond(),\n\t\ttm.Location(),\n\t)\n\treturn pcommon.NewTimestampFromTime(newTm)\n}\n"
  },
  {
    "path": "internal/storage/integration/dates_test.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc Test_dateOffsetNormalizer(t *testing.T) {\n\torigTime := time.Date(\n\t\t2024, time.January, 10,\n\t\t14, 30, 45, 123456789,\n\t\ttime.UTC,\n\t)\n\torigStartTime := time.Date(\n\t\t2017, time.January, 25,\n\t\t23, 56, 31, 639875000,\n\t\ttime.UTC,\n\t)\n\torigTs := pcommon.NewTimestampFromTime(origTime)\n\torigStartTs := pcommon.NewTimestampFromTime(origStartTime)\n\ttwoDaysAgo := -2\n\toneDayAgo := -1\n\tnow := time.Now()\n\texpectedTwoDaysAgo := now.UTC().AddDate(0, 0, twoDaysAgo)\n\texpectedOneDayAgo := now.UTC().AddDate(0, 0, oneDayAgo)\n\tnormalizer := newDateOffsetNormalizer(now)\n\ttd := ptrace.NewTraces()\n\tspan := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.SetStartTimestamp(origStartTs)\n\tspan.SetEndTimestamp(origTs)\n\tevent := span.Events().AppendEmpty()\n\tevent.SetTimestamp(origTs)\n\tnormalizer.normalizeTrace(td)\n\texpectedTwoDaysAgoTime := time.Date(\n\t\texpectedTwoDaysAgo.Year(),\n\t\texpectedTwoDaysAgo.Month(),\n\t\texpectedTwoDaysAgo.Day(),\n\t\torigStartTime.Hour(),\n\t\torigStartTime.Minute(),\n\t\torigStartTime.Second(),\n\t\torigStartTime.Nanosecond(),\n\t\ttime.UTC,\n\t)\n\texpectedOneDayAgoTime := time.Date(\n\t\texpectedOneDayAgo.Year(),\n\t\texpectedOneDayAgo.Month(),\n\t\texpectedOneDayAgo.Day(),\n\t\torigTime.Hour(),\n\t\torigTime.Minute(),\n\t\torigTime.Second(),\n\t\torigTime.Nanosecond(),\n\t\ttime.UTC,\n\t)\n\tassert.Equal(t, expectedTwoDaysAgoTime, span.StartTimestamp().AsTime())\n\tassert.Equal(t, expectedOneDayAgoTime, span.EndTimestamp().AsTime())\n\tassert.Equal(t, expectedOneDayAgoTime, event.Timestamp().AsTime())\n}\n"
  },
  {
    "path": "internal/storage/integration/elasticsearch_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\telasticsearch8 \"github.com/elastic/go-elasticsearch/v9\"\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jiter\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tesv2 \"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst (\n\thost                     = \"0.0.0.0\"\n\tqueryPort                = \"9200\"\n\tqueryHostPort            = host + \":\" + queryPort\n\tqueryURL                 = \"http://\" + queryHostPort\n\tindexPrefix              = \"integration-test\"\n\tindexDateLayout          = \"2006-01-02\"\n\ttagKeyDeDotChar          = \"@\"\n\tmaxSpanAge               = time.Hour * 72\n\tdefaultMaxDocCount       = 10_000\n\tspanTemplateName         = \"jaeger-span\"\n\tserviceTemplateName      = \"jaeger-service\"\n\tdependenciesTemplateName = \"jaeger-dependencies\"\n\tarchiveAliasSuffix       = \"archive\"\n)\n\ntype ESStorageIntegration struct {\n\tStorageIntegration\n\n\tclient   *elastic.Client\n\tv8Client *elasticsearch8.Client\n\n\tArchiveTraceReader tracestore.Reader\n\tArchiveTraceWriter tracestore.Writer\n\n\tfactory        *esv2.Factory\n\tarchiveFactory *esv2.Factory\n}\n\nfunc (s *ESStorageIntegration) getVersion() (uint, error) {\n\tpingResult, _, err := s.client.Ping(queryURL).Do(context.Background())\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tesVersion, err := strconv.Atoi(string(pingResult.Version.Number[0]))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\t// OpenSearch is based on ES 7.x\n\tif strings.Contains(pingResult.TagLine, \"OpenSearch\") {\n\t\tif pingResult.Version.Number[0] == '1' || pingResult.Version.Number[0] == '2' || pingResult.Version.Number[0] == '3' {\n\t\t\tesVersion = 7\n\t\t}\n\t}\n\treturn uint(esVersion), nil\n}\n\nfunc (s *ESStorageIntegration) initializeES(t *testing.T, c *http.Client, allTagsAsFields bool) {\n\trawClient, err := elastic.NewClient(\n\t\telastic.SetURL(queryURL),\n\t\telastic.SetSniff(false),\n\t\telastic.SetHttpClient(c))\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\trawClient.Stop()\n\t})\n\ts.client = rawClient\n\ts.v8Client, err = elasticsearch8.NewClient(elasticsearch8.Config{\n\t\tAddresses:            []string{queryURL},\n\t\tDiscoverNodesOnStart: false,\n\t\tTransport:            c.Transport,\n\t})\n\trequire.NoError(t, err)\n\n\ts.initSpanstore(t, allTagsAsFields)\n\n\ts.CleanUp = func(t *testing.T) {\n\t\ts.esCleanUp(t)\n\t}\n\ts.esCleanUp(t)\n}\n\nfunc (s *ESStorageIntegration) esCleanUp(t *testing.T) {\n\trequire.NoError(t, s.factory.Purge(context.Background()))\n\trequire.NoError(t, s.archiveFactory.Purge(context.Background()))\n}\n\nfunc (s *ESStorageIntegration) initSpanstore(t *testing.T, allTagsAsFields bool) {\n\tcfg := es.DefaultConfig()\n\tcfg.CreateIndexTemplates = true\n\tcfg.BulkProcessing = escfg.BulkProcessing{\n\t\tMaxActions:    1,\n\t\tFlushInterval: time.Nanosecond,\n\t}\n\tcfg.Tags.AllAsFields = allTagsAsFields\n\tcfg.ServiceCacheTTL = 1 * time.Second\n\tcfg.Indices.IndexPrefix = indexPrefix\n\tvar err error\n\tf, err := esv2.NewFactory(context.Background(), cfg, telemetry.NoopSettings(), nil)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\trequire.NoError(t, f.Close())\n\t})\n\tacfg := es.DefaultConfig()\n\tacfg.ReadAliasSuffix = archiveAliasSuffix\n\tacfg.WriteAliasSuffix = archiveAliasSuffix\n\tacfg.UseReadWriteAliases = true\n\tacfg.Tags.AllAsFields = allTagsAsFields\n\tacfg.Indices.IndexPrefix = indexPrefix\n\taf, err := esv2.NewFactory(context.Background(), acfg, telemetry.NoopSettings(), nil)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\trequire.NoError(t, af.Close())\n\t})\n\ts.factory = f\n\ts.archiveFactory = af\n\ts.TraceWriter, err = f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\ts.TraceReader, err = f.CreateTraceReader()\n\trequire.NoError(t, err)\n\ts.ArchiveTraceReader, err = af.CreateTraceReader()\n\trequire.NoError(t, err)\n\ts.ArchiveTraceWriter, err = af.CreateTraceWriter()\n\trequire.NoError(t, err)\n\ts.DependencyReader, err = f.CreateDependencyReader()\n\trequire.NoError(t, err)\n\ts.DependencyWriter = s.DependencyReader.(depstore.Writer)\n\ts.SamplingStore, err = f.CreateSamplingStore(1)\n\trequire.NoError(t, err)\n}\n\nfunc healthCheck(c *http.Client) error {\n\tfor range 200 {\n\t\tif resp, err := c.Get(queryURL); err == nil {\n\t\t\treturn resp.Body.Close()\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n\treturn errors.New(\"elastic search is not ready\")\n}\n\nfunc runElasticsearchTest(t *testing.T, allTagsAsFields bool) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tc := getESHttpClient(t)\n\trequire.NoError(t, healthCheck(c))\n\ts := &ESStorageIntegration{\n\t\tStorageIntegration: StorageIntegration{\n\t\t\tFixtures: LoadAndParseQueryTestCases(t, \"fixtures/queries_es.json\"),\n\t\t\t// TODO: remove this flag after ES supports returning spanKind\n\t\t\t//  Issue https://github.com/jaegertracing/jaeger/issues/1923\n\t\t\tGetOperationsMissingSpanKind: true,\n\t\t},\n\t}\n\ts.initializeES(t, c, allTagsAsFields)\n\ts.RunAll(t)\n\tt.Run(\"ArchiveTrace\", s.testArchiveTrace)\n}\n\nfunc TestElasticsearchStorage(t *testing.T) {\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\trunElasticsearchTest(t, false)\n}\n\nfunc TestElasticsearchStorage_AllTagsAsObjectFields(t *testing.T) {\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\trunElasticsearchTest(t, true)\n}\n\nfunc TestElasticsearchStorage_IndexTemplates(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\tc := getESHttpClient(t)\n\trequire.NoError(t, healthCheck(c))\n\ts := &ESStorageIntegration{}\n\ts.initializeES(t, c, true)\n\tesVersion, err := s.getVersion()\n\trequire.NoError(t, err)\n\t// TODO abstract this into pkg/es/client.IndexManagementLifecycleAPI\n\tif esVersion == 6 || esVersion == 7 {\n\t\tserviceTemplateExists, err := s.client.IndexTemplateExists(indexPrefix + \"-jaeger-service\").Do(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, serviceTemplateExists)\n\t\tspanTemplateExists, err := s.client.IndexTemplateExists(indexPrefix + \"-jaeger-span\").Do(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, spanTemplateExists)\n\t} else {\n\t\tserviceTemplateExistsResponse, err := s.v8Client.API.Indices.ExistsIndexTemplate(indexPrefix + \"-jaeger-service\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 200, serviceTemplateExistsResponse.StatusCode)\n\t\tspanTemplateExistsResponse, err := s.v8Client.API.Indices.ExistsIndexTemplate(indexPrefix + \"-jaeger-span\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, 200, spanTemplateExistsResponse.StatusCode)\n\t}\n\ts.cleanESIndexTemplates(t, indexPrefix)\n}\n\nfunc (s *ESStorageIntegration) cleanESIndexTemplates(t *testing.T, prefix string) error {\n\tversion, err := s.getVersion()\n\trequire.NoError(t, err)\n\tif version > 7 {\n\t\tprefixWithSeparator := prefix\n\t\tif prefix != \"\" {\n\t\t\tprefixWithSeparator += \"-\"\n\t\t}\n\t\t_, err := s.v8Client.Indices.DeleteIndexTemplate([]string{prefixWithSeparator + spanTemplateName})\n\t\trequire.NoError(t, err)\n\t\t_, err = s.v8Client.Indices.DeleteIndexTemplate([]string{prefixWithSeparator + serviceTemplateName})\n\t\trequire.NoError(t, err)\n\t\t_, err = s.v8Client.Indices.DeleteIndexTemplate([]string{prefixWithSeparator + dependenciesTemplateName})\n\t\trequire.NoError(t, err)\n\t} else {\n\t\t_, err := s.client.IndexDeleteTemplate(\"*\").Do(context.Background())\n\t\trequire.NoError(t, err)\n\t}\n\treturn nil\n}\n\n// testArchiveTrace validates that a trace with a start time older than maxSpanAge\n// can still be retrieved via the archive storage. This ensures archived traces are\n// accessible even when their age exceeds the retention period for primary storage.\n// This test applies only to Elasticsearch (ES) storage.\nfunc (s *ESStorageIntegration) testArchiveTrace(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\ttID := pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 22})\n\texpected := ptrace.NewTraces()\n\trs := expected.ResourceSpans().AppendEmpty()\n\trs.Resource().Attributes().PutStr(\"service.name\", \"archived_service\")\n\tss := rs.ScopeSpans().AppendEmpty()\n\tspan := ss.Spans().AppendEmpty()\n\tspan.SetName(\"archive_span\")\n\tspan.SetTraceID(tID)\n\tspan.SetSpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 55})\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-maxSpanAge * 5).Truncate(time.Microsecond)))\n\tspan.SetEndTimestamp(span.StartTimestamp())\n\trequire.NoError(t, s.ArchiveTraceWriter.WriteTraces(context.Background(), expected))\n\n\tvar actual ptrace.Traces\n\tfound := s.waitForCondition(t, func(_ *testing.T) bool {\n\t\titerTraces := s.ArchiveTraceReader.GetTraces(context.Background(), tracestore.GetTraceParams{TraceID: tID})\n\t\ttraces, err := jiter.CollectWithErrors(jptrace.AggregateTraces(iterTraces))\n\t\tif err != nil {\n\t\t\tt.Logf(\"Error loading trace: %v\", err)\n\t\t\treturn false\n\t\t}\n\t\tif len(traces) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tactual = traces[0]\n\t\treturn actual.SpanCount() >= expected.SpanCount()\n\t})\n\trequire.True(t, found)\n\tCompareTraces(t, expected, actual)\n}\n"
  },
  {
    "path": "internal/storage/integration/es_index_cleaner_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\n\telasticsearch8 \"github.com/elastic/go-elasticsearch/v9\"\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst (\n\tarchiveIndexName      = \"jaeger-span-archive\"\n\tdependenciesIndexName = \"jaeger-dependencies-2019-01-01\"\n\tsamplingIndexName     = \"jaeger-sampling-2019-01-01\"\n\tspanIndexName         = \"jaeger-span-2019-01-01\"\n\tserviceIndexName      = \"jaeger-service-2019-01-01\"\n\tindexCleanerImage     = \"localhost:5000/jaegertracing/jaeger-es-index-cleaner:local-test\"\n\trolloverImage         = \"localhost:5000/jaegertracing/jaeger-es-rollover:local-test\"\n\trolloverNowEnvVar     = `CONDITIONS='{\"max_age\":\"0s\"}'`\n)\n\nfunc TestIndexCleaner_doNotFailOnEmptyStorage(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\tclient, err := createESClient(t, getESHttpClient(t))\n\trequire.NoError(t, err)\n\t_, err = client.DeleteIndex(\"*\").Do(context.Background())\n\trequire.NoError(t, err)\n\n\ttests := []struct {\n\t\tenvs []string\n\t}{\n\t\t{envs: []string{\"ROLLOVER=false\"}},\n\t\t{envs: []string{\"ROLLOVER=true\"}},\n\t\t{envs: []string{\"ARCHIVE=true\"}},\n\t}\n\tfor _, test := range tests {\n\t\terr := runEsCleaner(7, test.envs)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestIndexCleaner_doNotFailOnFullStorage(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\tclient, err := createESClient(t, getESHttpClient(t))\n\trequire.NoError(t, err)\n\ttests := []struct {\n\t\tenvs []string\n\t}{\n\t\t{envs: []string{\"ROLLOVER=false\"}},\n\t\t{envs: []string{\"ROLLOVER=true\"}},\n\t\t{envs: []string{\"ARCHIVE=true\"}},\n\t}\n\tfor _, test := range tests {\n\t\t_, err = client.DeleteIndex(\"*\").Do(context.Background())\n\t\trequire.NoError(t, err)\n\t\t// Create Indices with adaptive sampling disabled (set to false).\n\t\terr := createAllIndices(client, \"\", false)\n\t\trequire.NoError(t, err)\n\t\terr = runEsCleaner(1500, test.envs)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestIndexCleaner(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\thcl := getESHttpClient(t)\n\tclient, err := createESClient(t, hcl)\n\trequire.NoError(t, err)\n\tv8Client, err := createESV8Client(hcl.Transport)\n\trequire.NoError(t, err)\n\n\ttests := []struct {\n\t\tname             string\n\t\tenvVars          []string\n\t\texpectedIndices  []string\n\t\tadaptiveSampling bool\n\t}{\n\t\t{\n\t\t\tname:    \"RemoveDailyIndices\",\n\t\t\tenvVars: []string{},\n\t\t\texpectedIndices: []string{\n\t\t\t\tarchiveIndexName,\n\t\t\t\t\"jaeger-span-000001\", \"jaeger-service-000001\", \"jaeger-dependencies-000001\", \"jaeger-span-000002\", \"jaeger-service-000002\", \"jaeger-dependencies-000002\",\n\t\t\t\t\"jaeger-span-archive-000001\", \"jaeger-span-archive-000002\",\n\t\t\t},\n\t\t\tadaptiveSampling: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"RemoveRolloverIndices\",\n\t\t\tenvVars: []string{\"ROLLOVER=true\"},\n\t\t\texpectedIndices: []string{\n\t\t\t\tarchiveIndexName, spanIndexName, serviceIndexName, dependenciesIndexName, samplingIndexName,\n\t\t\t\t\"jaeger-span-000002\", \"jaeger-service-000002\", \"jaeger-dependencies-000002\",\n\t\t\t\t\"jaeger-span-archive-000001\", \"jaeger-span-archive-000002\",\n\t\t\t},\n\t\t\tadaptiveSampling: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"RemoveArchiveIndices\",\n\t\t\tenvVars: []string{\"ARCHIVE=true\"},\n\t\t\texpectedIndices: []string{\n\t\t\t\tarchiveIndexName, spanIndexName, serviceIndexName, dependenciesIndexName, samplingIndexName,\n\t\t\t\t\"jaeger-span-000001\", \"jaeger-service-000001\", \"jaeger-dependencies-000001\", \"jaeger-span-000002\", \"jaeger-service-000002\", \"jaeger-dependencies-000002\",\n\t\t\t\t\"jaeger-span-archive-000002\",\n\t\t\t},\n\t\t\tadaptiveSampling: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"RemoveDailyIndices with adaptiveSampling\",\n\t\t\tenvVars: []string{},\n\t\t\texpectedIndices: []string{\n\t\t\t\tarchiveIndexName,\n\t\t\t\t\"jaeger-span-000001\", \"jaeger-service-000001\", \"jaeger-dependencies-000001\", \"jaeger-span-000002\", \"jaeger-service-000002\", \"jaeger-dependencies-000002\",\n\t\t\t\t\"jaeger-span-archive-000001\", \"jaeger-span-archive-000002\", \"jaeger-sampling-000001\", \"jaeger-sampling-000002\",\n\t\t\t},\n\t\t\tadaptiveSampling: true,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(fmt.Sprintf(\"%s_no_prefix, %s\", test.name, test.envVars), func(t *testing.T) {\n\t\t\trunIndexCleanerTest(t, client, v8Client, \"\", test.expectedIndices, test.envVars, test.adaptiveSampling)\n\t\t})\n\t\tt.Run(fmt.Sprintf(\"%s_prefix, %s\", test.name, test.envVars), func(t *testing.T) {\n\t\t\trunIndexCleanerTest(t, client, v8Client, indexPrefix, test.expectedIndices, append(test.envVars, \"INDEX_PREFIX=\"+indexPrefix), test.adaptiveSampling)\n\t\t})\n\t}\n}\n\nfunc runIndexCleanerTest(t *testing.T, client *elastic.Client, v8Client *elasticsearch8.Client, prefix string, expectedIndices, envVars []string, adaptiveSampling bool) {\n\t// make sure ES is clean\n\t_, err := client.DeleteIndex(\"*\").Do(context.Background())\n\trequire.NoError(t, err)\n\tdefer cleanESIndexTemplates(t, client, v8Client, prefix)\n\terr = createAllIndices(client, prefix, adaptiveSampling)\n\trequire.NoError(t, err)\n\terr = runEsCleaner(0, envVars)\n\trequire.NoError(t, err)\n\tfoundIndices, err := client.IndexNames()\n\trequire.NoError(t, err)\n\tif prefix != \"\" {\n\t\tprefix += \"-\"\n\t}\n\tvar actual []string\n\tfor _, index := range foundIndices {\n\t\t// ignore system indices https://github.com/jaegertracing/jaeger/issues/7002\n\t\tif strings.HasPrefix(index, prefix+\"jaeger\") {\n\t\t\tactual = append(actual, index)\n\t\t}\n\t}\n\tvar expected []string\n\tfor _, index := range expectedIndices {\n\t\texpected = append(expected, prefix+index)\n\t}\n\tassert.ElementsMatch(t, actual, expected, \"indices found: %v, expected: %v\", foundIndices, expected)\n}\n\nfunc createAllIndices(client *elastic.Client, prefix string, adaptiveSampling bool) error {\n\tprefixWithSeparator := prefix\n\tif prefix != \"\" {\n\t\tprefixWithSeparator += \"-\"\n\t}\n\t// create daily indices and archive index\n\terr := createEsIndices(client, []string{\n\t\tprefixWithSeparator + spanIndexName,\n\t\tprefixWithSeparator + serviceIndexName,\n\t\tprefixWithSeparator + dependenciesIndexName,\n\t\tprefixWithSeparator + samplingIndexName,\n\t\tprefixWithSeparator + archiveIndexName,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\t// create rollover archive index and roll alias to the new index\n\terr = runEsRollover(\"init\", []string{\"ARCHIVE=true\", \"INDEX_PREFIX=\" + prefix}, adaptiveSampling)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = runEsRollover(\"rollover\", []string{\"ARCHIVE=true\", \"INDEX_PREFIX=\" + prefix, rolloverNowEnvVar}, adaptiveSampling)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// create rollover main indices and roll over to the new index\n\terr = runEsRollover(\"init\", []string{\"ARCHIVE=false\", \"INDEX_PREFIX=\" + prefix}, adaptiveSampling)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = runEsRollover(\"rollover\", []string{\"ARCHIVE=false\", \"INDEX_PREFIX=\" + prefix, rolloverNowEnvVar}, adaptiveSampling)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc createEsIndices(client *elastic.Client, indices []string) error {\n\tfor _, index := range indices {\n\t\tif _, err := client.CreateIndex(index).Do(context.Background()); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc runEsCleaner(days int, envs []string) error {\n\tvar dockerEnv strings.Builder\n\tfor _, e := range envs {\n\t\tdockerEnv.WriteString(\" -e \")\n\t\tdockerEnv.WriteString(e)\n\t}\n\targs := fmt.Sprintf(\"docker run %s --rm --net=host %s %d http://%s\", dockerEnv.String(), indexCleanerImage, days, queryHostPort)\n\tcmd := exec.Command(\"/bin/sh\", \"-c\", args)\n\tout, err := cmd.CombinedOutput()\n\tfmt.Println(string(out))\n\treturn err\n}\n\nfunc runEsRollover(action string, envs []string, adaptiveSampling bool) error {\n\tvar dockerEnv strings.Builder\n\tfor _, e := range envs {\n\t\tdockerEnv.WriteString(\" -e \")\n\t\tdockerEnv.WriteString(e)\n\t}\n\targs := fmt.Sprintf(\"docker run %s --rm --net=host %s %s --adaptive-sampling=%t http://%s\", dockerEnv.String(), rolloverImage, action, adaptiveSampling, queryHostPort)\n\tcmd := exec.Command(\"/bin/sh\", \"-c\", args)\n\tout, err := cmd.CombinedOutput()\n\tfmt.Println(string(out))\n\treturn err\n}\n\nfunc createESClient(t *testing.T, hcl *http.Client) (*elastic.Client, error) {\n\tcl, err := elastic.NewClient(\n\t\telastic.SetURL(queryURL),\n\t\telastic.SetSniff(false),\n\t\telastic.SetHttpClient(hcl),\n\t)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tcl.Stop()\n\t})\n\treturn cl, nil\n}\n\nfunc createESV8Client(tr http.RoundTripper) (*elasticsearch8.Client, error) {\n\treturn elasticsearch8.NewClient(elasticsearch8.Config{\n\t\tAddresses:            []string{queryURL},\n\t\tDiscoverNodesOnStart: false,\n\t\tTransport:            tr,\n\t})\n}\n\nfunc cleanESIndexTemplates(t *testing.T, client *elastic.Client, v8Client *elasticsearch8.Client, prefix string) {\n\ts := &ESStorageIntegration{\n\t\tclient:   client,\n\t\tv8Client: v8Client,\n\t}\n\ts.cleanESIndexTemplates(t, prefix)\n}\n\nfunc getESHttpClient(t *testing.T) *http.Client {\n\ttr := &http.Transport{}\n\tt.Cleanup(func() {\n\t\ttr.CloseIdleConnections()\n\t})\n\treturn &http.Client{Transport: tr}\n}\n"
  },
  {
    "path": "internal/storage/integration/es_index_rollover_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst (\n\tdefaultILMPolicyName = \"jaeger-ilm-policy\"\n)\n\nfunc TestIndexRollover_FailIfILMNotPresent(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\tclient, err := createESClient(t, getESHttpClient(t))\n\trequire.NoError(t, err)\n\trequire.NoError(t, err)\n\t// make sure ES is clean\n\tcleanES(t, client, defaultILMPolicyName)\n\tenvVars := []string{\"ES_USE_ILM=true\"}\n\t// Run the ES rollover test with adaptive sampling disabled (set to false).\n\terr = runEsRollover(\"init\", envVars, false)\n\trequire.EqualError(t, err, \"exit status 1\")\n\tindices, err := client.IndexNames()\n\trequire.NoError(t, err)\n\tassert.Empty(t, indices)\n}\n\nfunc TestIndexRollover_Idempotency(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\tclient, err := createESClient(t, getESHttpClient(t))\n\trequire.NoError(t, err)\n\t// Make sure that es is clean before the test!\n\tcleanES(t, client, defaultILMPolicyName)\n\terr = runEsRollover(\"init\", []string{}, false)\n\trequire.NoError(t, err)\n\t// Run again and it should return without any error\n\terr = runEsRollover(\"init\", []string{}, false)\n\trequire.NoError(t, err)\n\tcleanES(t, client, defaultILMPolicyName)\n}\n\nfunc TestIndexRollover_CreateIndicesWithILM(t *testing.T) {\n\tSkipUnlessEnv(t, \"elasticsearch\", \"opensearch\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnceForES(t)\n\t})\n\t// Test using the default ILM Policy Name, i.e. do not pass the ES_ILM_POLICY_NAME env var to the rollover script.\n\tt.Run(\"DefaultPolicyName\", func(t *testing.T) {\n\t\trunCreateIndicesWithILM(t, defaultILMPolicyName)\n\t})\n\n\t// Test using a configured ILM Policy Name, i.e. pass the ES_ILM_POLICY_NAME env var to the rollover script.\n\tt.Run(\"SetPolicyName\", func(t *testing.T) {\n\t\trunCreateIndicesWithILM(t, \"jaeger-test-policy\")\n\t})\n}\n\nfunc runCreateIndicesWithILM(t *testing.T, ilmPolicyName string) {\n\tclient, err := createESClient(t, getESHttpClient(t))\n\trequire.NoError(t, err)\n\tesVersion, err := getVersion(client)\n\trequire.NoError(t, err)\n\n\tenvVars := []string{\n\t\t\"ES_USE_ILM=true\",\n\t}\n\n\tif ilmPolicyName != defaultILMPolicyName {\n\t\tenvVars = append(envVars, \"ES_ILM_POLICY_NAME=\"+ilmPolicyName)\n\t}\n\n\tif esVersion >= 7 {\n\t\texpectedIndices := []string{\"jaeger-span-000001\", \"jaeger-service-000001\", \"jaeger-dependencies-000001\"}\n\t\tt.Run(\"NoPrefix\", func(t *testing.T) {\n\t\t\trunIndexRolloverWithILMTest(t, client, \"\", expectedIndices, envVars, ilmPolicyName, false)\n\t\t})\n\t\tt.Run(\"WithPrefix\", func(t *testing.T) {\n\t\t\trunIndexRolloverWithILMTest(t, client, indexPrefix, expectedIndices, append(envVars, \"INDEX_PREFIX=\"+indexPrefix), ilmPolicyName, false)\n\t\t})\n\t\tt.Run(\"WithAdaptiveSampling\", func(t *testing.T) {\n\t\t\trunIndexRolloverWithILMTest(t, client, indexPrefix, expectedIndices, append(envVars, \"INDEX_PREFIX=\"+indexPrefix), ilmPolicyName, true)\n\t\t})\n\t}\n}\n\nfunc runIndexRolloverWithILMTest(t *testing.T, client *elastic.Client, prefix string, expectedIndices, envVars []string, ilmPolicyName string, adaptiveSampling bool) {\n\twriteAliases := []string{\"jaeger-service-write\", \"jaeger-span-write\", \"jaeger-dependencies-write\"}\n\tif adaptiveSampling {\n\t\twriteAliases = append(writeAliases, \"jaeger-sampling-write\")\n\t\texpectedIndices = append(expectedIndices, \"jaeger-sampling-000001\")\n\t}\n\t// make sure ES is cleaned before test\n\tcleanES(t, client, ilmPolicyName)\n\tv8Client, err := createESV8Client(getESHttpClient(t).Transport)\n\trequire.NoError(t, err)\n\t// make sure ES is cleaned after test\n\tdefer cleanES(t, client, ilmPolicyName)\n\tdefer cleanESIndexTemplates(t, client, v8Client, prefix)\n\terr = createILMPolicy(client, ilmPolicyName)\n\trequire.NoError(t, err)\n\n\tif prefix != \"\" {\n\t\tprefix += \"-\"\n\t}\n\tvar expected, expectedWriteAliases, actualWriteAliases []string\n\tfor _, index := range expectedIndices {\n\t\texpected = append(expected, prefix+index)\n\t}\n\tfor _, alias := range writeAliases {\n\t\texpectedWriteAliases = append(expectedWriteAliases, prefix+alias)\n\t}\n\n\t// Run rollover with given EnvVars\n\terr = runEsRollover(\"init\", envVars, adaptiveSampling)\n\trequire.NoError(t, err)\n\n\tindices, err := client.IndexNames()\n\trequire.NoError(t, err)\n\n\t// Get ILM Policy Attached\n\tsettings, err := client.IndexGetSettings(expected...).FlatSettings(true).Do(context.Background())\n\trequire.NoError(t, err)\n\t// Check ILM Policy is attached and Get rollover alias attached\n\tfor _, v := range settings {\n\t\tassert.Equal(t, ilmPolicyName, v.Settings[\"index.lifecycle.name\"])\n\t\tactualWriteAliases = append(actualWriteAliases, v.Settings[\"index.lifecycle.rollover_alias\"].(string))\n\t}\n\t// Check indices created\n\tassert.ElementsMatch(t, indices, expected)\n\t// Check rollover alias is write alias\n\tassert.ElementsMatch(t, actualWriteAliases, expectedWriteAliases)\n}\n\nfunc getVersion(client *elastic.Client) (uint, error) {\n\tpingResult, _, err := client.Ping(queryURL).Do(context.Background())\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tesVersion, err := strconv.Atoi(string(pingResult.Version.Number[0]))\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn uint(esVersion), nil\n}\n\nfunc createILMPolicy(client *elastic.Client, policyName string) error {\n\t_, err := client.XPackIlmPutLifecycle().Policy(policyName).BodyString(`{\"policy\": {\"phases\": {\"hot\": {\"min_age\": \"0ms\",\"actions\": {\"rollover\": {\"max_age\": \"1d\"},\"set_priority\": {\"priority\": 100}}}}}}`).Do(context.Background())\n\treturn err\n}\n\nfunc cleanES(t *testing.T, client *elastic.Client, policyName string) {\n\t_, err := client.DeleteIndex(\"*\").Do(context.Background())\n\trequire.NoError(t, err)\n\tesVersion, err := getVersion(client)\n\trequire.NoError(t, err)\n\tif esVersion >= 7 {\n\t\t_, err = client.XPackIlmDeleteLifecycle().Policy(policyName).Do(context.Background())\n\t\tif err != nil && !elastic.IsNotFound(err) {\n\t\t\tassert.Fail(t, \"Not able to clean up ILM Policy\")\n\t\t}\n\t}\n\t_, err = client.IndexDeleteTemplate(\"*\").Do(context.Background())\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "internal/storage/integration/fixtures/grpc_plugin_conf.yaml",
    "content": "enable_streaming_writer: true"
  },
  {
    "path": "internal/storage/integration/fixtures/queries.json",
    "content": "[\n  {\n    \"Caption\": \"Tags in one spot - Tags\",\n    \"Query\": {\n      \"ServiceName\": \"query01-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"span_tags_trace\"]\n  },\n  {\n    \"Caption\": \"Tags in one spot - Logs\",\n    \"Query\": {\n      \"ServiceName\": \"query02-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"log_tags_trace\"]\n  },\n  {\n    \"Caption\": \"Tags in one spot - Process\",\n    \"Query\": {\n      \"ServiceName\": \"query03-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"process_tags_trace\"]\n  },\n  {\n    \"Caption\": \"Tags in different spots\",\n    \"Query\": {\n      \"ServiceName\": \"query04-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multi_spot_tags_trace\"]\n  },\n  {\n    \"Caption\": \"Trace spans over multiple indices\",\n    \"Query\": {\n      \"ServiceName\": \"query05-service\",\n      \"OperationName\": \"\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T00:00:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T00:07:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multi_index_trace\"]\n  },\n  {\n    \"Caption\": \"Operation name\",\n    \"Query\": {\n      \"ServiceName\": \"query06-service\",\n      \"OperationName\": \"query06-operation\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"opname_trace\"]\n  },\n  {\n    \"Caption\": \"Operation name + max Duration\",\n    \"Query\": {\n      \"ServiceName\": \"query07-service\",\n      \"OperationName\": \"query07-operation\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 2000,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"opname_maxdur_trace\"]\n  },\n  {\n    \"Caption\": \"Operation name + Duration range\",\n    \"Query\": {\n      \"ServiceName\": \"query08-service\",\n      \"OperationName\": \"query08-operation\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 4500,\n      \"DurationMax\": 5500,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"opname_dur_trace\"]\n  },\n  {\n    \"Caption\": \"Duration range\",\n    \"Query\": {\n      \"ServiceName\": \"query09-service\",\n      \"OperationName\": \"\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 4500,\n      \"DurationMax\": 5500,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"dur_trace\"]\n  },\n  {\n    \"Caption\": \"max Duration\",\n    \"Query\": {\n      \"ServiceName\": \"query10-service\",\n      \"OperationName\": \"\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 1000,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"max_dur_trace\"]\n  },\n  {\n    \"Caption\": \"default\",\n    \"Query\": {\n      \"ServiceName\": \"query11-service\",\n      \"OperationName\": \"\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"default\"]\n  },\n  {\n    \"Caption\": \"Tags + Operation name\",\n    \"Query\": {\n      \"ServiceName\": \"query12-service\",\n      \"OperationName\": \"query12-operation\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"tags_opname_trace\"]\n  },\n  {\n    \"Caption\": \"Tags + Operation name + max Duration\",\n    \"Query\": {\n      \"ServiceName\": \"query13-service\",\n      \"OperationName\": \"query13-operation\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 2000,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"tags_opname_maxdur_trace\"]\n  },\n  {\n    \"Caption\": \"Tags + Operation name + Duration range\",\n    \"Query\": {\n      \"ServiceName\": \"query14-service\",\n      \"OperationName\": \"query14-operation\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 4500,\n      \"DurationMax\": 5500,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"tags_opname_dur_trace\"]\n  },\n  {\n    \"Caption\": \"Tags + Duration range\",\n    \"Query\": {\n      \"ServiceName\": \"query15-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 4500,\n      \"DurationMax\": 5500,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"tags_dur_trace\"]\n  },\n  {\n    \"Caption\": \"Tags + max Duration\",\n    \"Query\": {\n      \"ServiceName\": \"query16-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 1000,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"tags_maxdur_trace\"]\n  },\n  {\n    \"Caption\": \"Multi-spot Tags + Operation name\",\n    \"Query\": {\n      \"ServiceName\": \"query17-service\",\n      \"OperationName\": \"query17-operation\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multispottag_opname_trace\"]\n  },\n  {\n    \"Caption\": \"Multi-spot Tags + Operation name + max Duration\",\n    \"Query\": {\n      \"ServiceName\": \"query18-service\",\n      \"OperationName\": \"query18-operation\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 2000,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multispottag_opname_maxdur_trace\"]\n  },\n  {\n    \"Caption\": \"Multi-spot Tags + Operation name + Duration range\",\n    \"Query\": {\n      \"ServiceName\": \"query19-service\",\n      \"OperationName\": \"query19-operation\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 4500,\n      \"DurationMax\": 5500,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multispottag_opname_dur_trace\"]\n  },\n  {\n    \"Caption\": \"Multi-spot Tags + Duration range\",\n    \"Query\": {\n      \"ServiceName\": \"query20-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 4500,\n      \"DurationMax\": 5500,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multispottag_dur_trace\"]\n  },\n  {\n    \"Caption\": \"Multi-spot Tags + max Duration\",\n    \"Query\": {\n      \"ServiceName\": \"query21-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 1000,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multispottag_maxdur_trace\"]\n  },\n  {\n    \"Caption\": \"Multiple Traces\",\n    \"Query\": {\n      \"ServiceName\": \"query22-service\",\n      \"OperationName\": \"\",\n      \"Tags\": null,\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"multiple1_trace\", \"multiple2_trace\", \"multiple3_trace\"]\n  },\n  {\n    \"Caption\": \"Scope Name and Version\",\n    \"Query\": {\n      \"ServiceName\": \"query23-service\",\n      \"OperationName\": \"\",\n      \"Tags\": {\n        \"sameplacetag1\":\"sameplacevalue\",\n        \"sameplacetag2\":123,\n        \"sameplacetag3\":72.5,\n        \"sameplacetag4\":true\n      },\n      \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n      \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n      \"DurationMin\": 0,\n      \"DurationMax\": 0,\n      \"NumTraces\": 1000\n    },\n    \"ExpectedFixtures\": [\"scope_name_version_trace\"]\n  }\n]\n"
  },
  {
    "path": "internal/storage/integration/fixtures/queries_es.json",
    "content": "[\n    {\n        \"Caption\": \"Tag escaped operator + Operation name + max Duration\",\n        \"Query\": {\n        \"ServiceName\": \"query23-service\",\n        \"OperationName\": \"query23-operation\",\n        \"Tags\": {\n            \"sameplacetag1\":\"same\\\\*\"\n        },\n        \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n        \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n        \"DurationMin\": 0,\n        \"DurationMax\": 1000,\n        \"NumTraces\": 1000\n        },\n        \"ExpectedFixtures\": [\"tags_escaped_operator_trace_1\"]\n    },\n    {\n        \"Caption\": \"Tag wildcard regex\",\n        \"Query\": {\n        \"ServiceName\": \"query24-service\",\n        \"OperationName\": \"\",\n        \"Tags\": {\n            \"sameplacetag1\":\"same.*\"\n        },\n        \"StartTimeMin\": \"2017-01-26T15:46:31.639875Z\",\n        \"StartTimeMax\": \"2017-01-26T17:46:31.639875Z\",\n        \"DurationMin\": 0,\n        \"DurationMax\": 0,\n        \"NumTraces\": 1000\n        },\n        \"ExpectedFixtures\": [\"tags_wildcard_regex_1\", \"tags_wildcard_regex_2\"]\n    }\n]"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/default.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query11-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000011\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"query11-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query09-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000009\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/example_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"example-service-2\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000011\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"example-operation-2\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"example-service-3\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000011\",\n              \"spanId\": \"0000000000000005\",\n              \"name\": \"example-operation-1\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"example-service-1\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000011\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"example-operation-1\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            },\n            {\n              \"traceId\": \"00000000000000000000000000000011\",\n              \"spanId\": \"0000000000000006\",\n              \"name\": \"example-operation-3\",\n              \"kind\": 2,\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            },\n            {\n              \"traceId\": \"00000000000000000000000000000011\",\n              \"spanId\": \"0000000000000007\",\n              \"name\": \"example-operation-4\",\n              \"kind\": 3,\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/log_tags_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query02-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000002\",\n              \"spanId\": \"0000000000000001\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag1\",\n                      \"value\": {\n                        \"stringValue\": \"sameplacevalue\"\n                      }\n                    },\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    },\n                    {\n                      \"key\": \"sameplacetag4\",\n                      \"value\": {\n                        \"boolValue\": true\n                      }\n                    },\n                    {\n                      \"key\": \"sameplacetag3\",\n                      \"value\": {\n                        \"doubleValue\": 72.5\n                      }\n                    },\n                    {\n                      \"key\": \"blob\",\n                      \"value\": {\n                        \"bytesValue\": \"AAAwOQ==\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/max_dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query10-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000010\",\n              \"spanId\": \"0000000000000002\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multi_index_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query05-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000005\",\n              \"spanId\": \"0000000000000001\",\n              \"name\": \"operation-list-test2\",\n              \"startTimeUnixNano\": \"1485389011639875000\",\n              \"endTimeUnixNano\": \"1485389011639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            },\n            {\n              \"traceId\": \"00000000000000000000000000000005\",\n              \"spanId\": \"0000000000000002\",\n              \"name\": \"operation-list-test3\",\n              \"startTimeUnixNano\": \"1485388591639875000\",\n              \"endTimeUnixNano\": \"1485388591639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multi_spot_tags_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query04-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag1\",\n            \"value\": {\n              \"stringValue\": \"sameplacevalue\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000004\",\n              \"spanId\": \"0000000000000001\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag4\",\n                  \"value\": {\n                    \"boolValue\": true\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag3\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multiple1_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query22-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000221\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"query22-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multiple2_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query22-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000222\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"query22-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multiple3_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query22-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000223\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"query22-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639975000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multispottag_dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query20-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          },\n          {\n            \"key\": \"blob\",\n            \"value\": {\n              \"bytesValue\": \"AAAwOQ==\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000020\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag2\",\n                  \"value\": {\n                    \"intValue\": \"123\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag4\",\n                  \"value\": {\n                    \"boolValue\": true\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag1\",\n                      \"value\": {\n                        \"stringValue\": \"sameplacevalue\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multispottag_maxdur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query21-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag4\",\n            \"value\": {\n              \"boolValue\": true\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000021\",\n              \"spanId\": \"0000000000000005\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"blob\",\n                      \"value\": {\n                        \"bytesValue\": \"AAAwOQ==\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multispottag_opname_dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query19-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag4\",\n            \"value\": {\n              \"boolValue\": true\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000019\",\n              \"spanId\": \"0000000000000005\",\n              \"name\": \"query19-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"blob\",\n                      \"value\": {\n                        \"bytesValue\": \"AAAwOQ==\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multispottag_opname_maxdur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query18-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000018\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"query18-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag4\",\n                      \"value\": {\n                        \"boolValue\": true\n                      }\n                    },\n                    {\n                      \"key\": \"blob\",\n                      \"value\": {\n                        \"bytesValue\": \"AAAwOQ==\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/multispottag_opname_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query17-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag1\",\n            \"value\": {\n              \"stringValue\": \"sameplacevalue\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000017\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"query17-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag3\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag4\",\n                      \"value\": {\n                        \"boolValue\": true\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/opname_dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query08-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000008\",\n              \"spanId\": \"0000000000000002\",\n              \"name\": \"query08-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/opname_maxdur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query07-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000007\",\n              \"spanId\": \"0000000000000003\",\n              \"parentSpanId\": \"0000000000000002\",\n              \"name\": \"query07-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"status\": {}\n            },\n            {\n              \"traceId\": \"00000000000000000000000000000007\",\n              \"spanId\": \"0000000000000002\",\n              \"name\": \"query07-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639877000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/opname_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query06-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000006\",\n              \"spanId\": \"0000000000000001\",\n              \"name\": \"query06-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/process_tags_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query03-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag1\",\n            \"value\": {\n              \"stringValue\": \"sameplacevalue\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag2\",\n            \"value\": {\n              \"intValue\": \"123\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag4\",\n            \"value\": {\n              \"boolValue\": true\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          },\n          {\n            \"key\": \"blob\",\n            \"value\": {\n              \"bytesValue\": \"AAAwOQ==\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000003\",\n              \"spanId\": \"0000000000000001\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/scope_name_version_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query23-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {\n            \"name\": \"testing-library\",\n            \"version\": \"1.1.1\"\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000224\",\n              \"spanId\": \"0000000000000002\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag2\",\n                  \"value\": {\n                    \"intValue\": \"123\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag4\",\n                  \"value\": {\n                    \"boolValue\": true\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag3\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/span_tags_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query01-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000001\",\n              \"spanId\": \"0000000000000002\",\n              \"name\": \"some-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639882000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag2\",\n                  \"value\": {\n                    \"intValue\": \"123\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag4\",\n                  \"value\": {\n                    \"boolValue\": true\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag3\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query15-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000015\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"placeholder\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag2\",\n                  \"value\": {\n                    \"intValue\": \"123\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag4\",\n                  \"value\": {\n                    \"boolValue\": true\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag3\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_escaped_operator_trace_1.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query23-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000512\",\n              \"spanId\": \"0000000000000005\",\n              \"name\": \"query23-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"same*\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_escaped_operator_trace_2.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query23-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000005912\",\n              \"spanId\": \"0000000000000005\",\n              \"name\": \"query23-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacedifferentvalue\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_maxdur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query16-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag1\",\n            \"value\": {\n              \"stringValue\": \"sameplacevalue\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag2\",\n            \"value\": {\n              \"intValue\": \"123\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag4\",\n            \"value\": {\n              \"boolValue\": true\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          },\n          {\n            \"key\": \"blob\",\n            \"value\": {\n              \"bytesValue\": \"AAAwOQ==\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000016\",\n              \"spanId\": \"0000000000000005\",\n              \"name\": \"query16-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_opname_dur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query14-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000014\",\n              \"spanId\": \"0000000000000003\",\n              \"name\": \"query14-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639880000\",\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"sameplacetag1\",\n                      \"value\": {\n                        \"stringValue\": \"sameplacevalue\"\n                      }\n                    },\n                    {\n                      \"key\": \"sameplacetag2\",\n                      \"value\": {\n                        \"intValue\": \"123\"\n                      }\n                    },\n                    {\n                      \"key\": \"sameplacetag4\",\n                      \"value\": {\n                        \"boolValue\": true\n                      }\n                    },\n                    {\n                      \"key\": \"sameplacetag3\",\n                      \"value\": {\n                        \"doubleValue\": 72.5\n                      }\n                    },\n                    {\n                      \"key\": \"blob\",\n                      \"value\": {\n                        \"bytesValue\": \"AAAwOQ==\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\"\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_opname_maxdur_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query13-service\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag1\",\n            \"value\": {\n              \"stringValue\": \"sameplacevalue\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag2\",\n            \"value\": {\n              \"intValue\": \"123\"\n            }\n          },\n          {\n            \"key\": \"sameplacetag4\",\n            \"value\": {\n              \"boolValue\": true\n            }\n          },\n          {\n            \"key\": \"sameplacetag3\",\n            \"value\": {\n              \"doubleValue\": 72.5\n            }\n          },\n          {\n            \"key\": \"blob\",\n            \"value\": {\n              \"bytesValue\": \"AAAwOQ==\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000013\",\n              \"spanId\": \"0000000000000007\",\n              \"name\": \"query13-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639876000\",\n              \"attributes\": [\n                {\n                  \"key\": \"tag1\",\n                  \"value\": {\n                    \"stringValue\": \"value1\"\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"tag3\",\n                      \"value\": {\n                        \"stringValue\": \"value3\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485449191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"something\",\n                      \"value\": {\n                        \"stringValue\": \"blah\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_opname_trace.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query12-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000012\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"query12-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639877000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag2\",\n                  \"value\": {\n                    \"intValue\": \"123\"\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag4\",\n                  \"value\": {\n                    \"boolValue\": true\n                  }\n                },\n                {\n                  \"key\": \"sameplacetag3\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"000000000000000000000000000000ff\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000000000000000000001\",\n                  \"spanId\": \"0000000000000002\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000000000000000000001\",\n                  \"spanId\": \"0000000000000002\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_wildcard_regex_1.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query24-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000000a12\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"query24-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639877000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue1\"\n                  }\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/fixtures/traces/tags_wildcard_regex_2.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"query24-service\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {},\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000000000000000001212\",\n              \"spanId\": \"0000000000000004\",\n              \"name\": \"query24-operation\",\n              \"startTimeUnixNano\": \"1485449191639875000\",\n              \"endTimeUnixNano\": \"1485449191639877000\",\n              \"attributes\": [\n                {\n                  \"key\": \"sameplacetag1\",\n                  \"value\": {\n                    \"stringValue\": \"sameplacevalue2\"\n                  }\n                }\n              ],\n              \"status\": {}\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/integration/grpc_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/grpc\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n\t\"github.com/jaegertracing/jaeger/ports\"\n)\n\ntype GRPCStorageIntegrationTestSuite struct {\n\tStorageIntegration\n\tflags         []string\n\tfactory       *grpc.Factory\n\tremoteStorage *RemoteMemoryStorage\n}\n\nfunc (s *GRPCStorageIntegrationTestSuite) initialize(t *testing.T) {\n\ts.remoteStorage = StartNewRemoteMemoryStorage(t, ports.RemoteStorageGRPC)\n\n\tf, err := grpc.NewFactory(\n\t\tcontext.Background(),\n\t\tgrpc.Config{\n\t\t\tClientConfig: configgrpc.ClientConfig{\n\t\t\t\tEndpoint: \"localhost:17271\",\n\t\t\t\tTLS: configtls.ClientConfig{\n\t\t\t\t\tInsecure: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\ttelemetry.NoopSettings(),\n\t)\n\trequire.NoError(t, err)\n\ts.factory = f\n\n\ts.TraceWriter, err = f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\ts.TraceReader, err = f.CreateTraceReader()\n\trequire.NoError(t, err)\n\n\t// TODO DependencyWriter is not implemented in grpc store\n\n\ts.CleanUp = s.cleanUp\n}\n\nfunc (s *GRPCStorageIntegrationTestSuite) close(t *testing.T) {\n\trequire.NoError(t, s.factory.Close())\n\ts.remoteStorage.Close(t)\n}\n\nfunc (s *GRPCStorageIntegrationTestSuite) cleanUp(t *testing.T) {\n\ts.close(t)\n\ts.initialize(t)\n}\n\nfunc TestGRPCRemoteStorage(t *testing.T) {\n\tSkipUnlessEnv(t, \"grpc\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnce(t)\n\t})\n\ts := &GRPCStorageIntegrationTestSuite{\n\t\tflags: []string{\n\t\t\t\"--grpc-storage.server=localhost:17271\",\n\t\t\t\"--grpc-storage.tls.enabled=false\",\n\t\t},\n\t}\n\ts.initialize(t)\n\tdefer s.close(t)\n\ts.RunAll(t)\n}\n"
  },
  {
    "path": "internal/storage/integration/integration.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"embed\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jiter\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\tsamplemodel \"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\n//go:embed fixtures\nvar fixtures embed.FS\n\n// StorageIntegration holds components for storage integration test.\n// The intended usage is as follows:\n// - a specific storage implementation declares its own test functions\n// - in those functions it instantiates and populates this struct\n// - it then calls RunAll.\n//\n// Some implementations may declare multiple tests, with different settings,\n// and RunAll() under different conditions.\ntype StorageIntegration struct {\n\tTraceWriter      tracestore.Writer\n\tTraceReader      tracestore.Reader\n\tDependencyWriter depstore.Writer\n\tDependencyReader depstore.Reader\n\tSamplingStore    samplingstore.Store\n\tFixtures         []*QueryFixtures\n\n\t// TODO: remove this after all storage backends return spanKind from GetOperations\n\tGetOperationsMissingSpanKind bool\n\n\t// TODO: remove this after all storage backends return Source column from GetDependencies\n\n\tGetDependenciesReturnsSource bool\n\n\t// List of tests which has to be skipped, it can be regex too.\n\tSkipList []string\n\n\t// CleanUp() should ensure that the storage backend is clean before another test.\n\t// called either before or after each test, and should be idempotent\n\tCleanUp func(t *testing.T)\n}\n\n// === SpanStore Integration Tests ===\n\ntype Query struct {\n\tServiceName   string\n\tOperationName string\n\tTags          map[string]any\n\tStartTimeMin  time.Time\n\tStartTimeMax  time.Time\n\tDurationMin   time.Duration\n\tDurationMax   time.Duration\n\tNumTraces     int\n}\n\nfunc (q *Query) ToTraceQueryParams(t *testing.T) *tracestore.TraceQueryParams {\n\tattributes := pcommon.NewMap()\n\tfor k, v := range q.Tags {\n\t\tswitch v := v.(type) {\n\t\tcase string:\n\t\t\tattributes.PutStr(k, v)\n\t\tcase int:\n\t\t\tattributes.PutInt(k, int64(v))\n\t\tcase float64:\n\t\t\tattributes.PutDouble(k, v)\n\t\tcase bool:\n\t\t\tattributes.PutBool(k, v)\n\t\tdefault:\n\t\t\tt.Fatalf(\"Unsupported tag value type: %T\", v)\n\t\t}\n\t}\n\n\treturn &tracestore.TraceQueryParams{\n\t\tServiceName:   q.ServiceName,\n\t\tOperationName: q.OperationName,\n\t\tAttributes:    attributes,\n\t\tStartTimeMin:  q.StartTimeMin,\n\t\tStartTimeMax:  q.StartTimeMax,\n\t\tDurationMin:   q.DurationMin,\n\t\tDurationMax:   q.DurationMax,\n\t\tSearchDepth:   q.NumTraces,\n\t}\n}\n\n// QueryFixtures and TraceFixtures are under ./fixtures/queries.json and ./fixtures/traces/*.json respectively.\n// Each query fixture includes:\n// - Caption: describes the query we are testing\n// - Query: the query we are testing\n// - ExpectedFixture: the trace fixture that we want back from these queries.\n// Queries are not necessarily numbered, but since each query requires a service name,\n// the service name is formatted \"query##-service\".\ntype QueryFixtures struct {\n\tCaption          string\n\tQuery            *Query\n\tExpectedFixtures []string\n}\n\nfunc (s *StorageIntegration) cleanUp(t *testing.T) {\n\trequire.NotNil(t, s.CleanUp, \"CleanUp function must be provided\")\n\ts.CleanUp(t)\n}\n\nfunc SkipUnlessEnv(t *testing.T, storage ...string) {\n\tenv := os.Getenv(\"STORAGE\")\n\tif slices.Contains(storage, env) {\n\t\treturn\n\t}\n\tt.Skipf(\"This test requires environment variable STORAGE=%s\", strings.Join(storage, \"|\"))\n}\n\nvar CassandraSkippedTests = []string{\n\t\"Tags_+_Operation_name_+_Duration_range\",\n\t\"Tags_+_Duration_range\",\n\t\"Tags_+_Operation_name_+_max_Duration\",\n\t\"Tags_+_max_Duration\",\n\t\"Operation_name_+_Duration_range\",\n\t\"Duration_range\",\n\t\"max_Duration\",\n\t\"Multiple_Traces\",\n}\n\nfunc (s *StorageIntegration) skipIfNeeded(t *testing.T) {\n\tfor _, pat := range s.SkipList {\n\t\tescapedPat := regexp.QuoteMeta(pat)\n\t\tok, err := regexp.MatchString(escapedPat, t.Name())\n\t\trequire.NoError(t, err)\n\t\tif ok {\n\t\t\tt.Skip()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (*StorageIntegration) waitForCondition(t *testing.T, predicate func(t *testing.T) bool) bool {\n\tconst iterations = 100 // Will wait at most 100 seconds.\n\tfor i := range iterations {\n\t\tif predicate(t) {\n\t\t\treturn true\n\t\t}\n\t\tt.Logf(\"Waiting for storage backend to update documents, iteration %d out of %d\", i+1, iterations)\n\t\ttime.Sleep(time.Second)\n\t}\n\treturn predicate(t)\n}\n\nfunc (s *StorageIntegration) testGetServices(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\n\texpected := []string{\"example-service-1\", \"example-service-2\", \"example-service-3\"}\n\ts.loadParseAndWriteExampleTrace(t)\n\n\tvar actual []string\n\tfound := s.waitForCondition(t, func(t *testing.T) bool {\n\t\tvar err error\n\t\tactual, err = s.TraceReader.GetServices(context.Background())\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\tsort.Strings(actual)\n\t\tt.Logf(\"Retrieved services: %v\", actual)\n\t\tif len(actual) > len(expected) {\n\t\t\t// If the storage backend returns more services than expected, let's log traces for those\n\t\t\tt.Log(\"🛑 Found unexpected services!\")\n\t\t\tfor _, service := range actual {\n\t\t\t\titerTraces := s.TraceReader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\t\t\t\tServiceName:  service,\n\t\t\t\t\tStartTimeMin: time.Now().Add(-2 * time.Hour),\n\t\t\t\t\tStartTimeMax: time.Now(),\n\t\t\t\t})\n\t\t\t\tfor traces, err := range iterTraces {\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Log(err)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tfor _, trace := range traces {\n\t\t\t\t\t\tfor _, span := range jptrace.SpanIter(trace) {\n\t\t\t\t\t\t\tt.Logf(\"span: Service: %s, TraceID: %s, Operation: %s\", service, span.TraceID(), span.Name())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn assert.ObjectsAreEqualValues(expected, actual)\n\t})\n\n\tif !assert.True(t, found) {\n\t\tt.Log(\"\\t Expected:\", expected)\n\t\tt.Log(\"\\t Actual  :\", actual)\n\t}\n}\n\nfunc (s *StorageIntegration) helperTestGetTrace(\n\tt *testing.T,\n\ttraceSize int,\n\tduplicateCount int,\n\ttestName string,\n\tvalidator func(t *testing.T, actual ptrace.Traces),\n) {\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\n\tt.Logf(\"Testing %s...\", testName)\n\n\texpected := s.writeLargeTraceWithDuplicateSpanIds(t, traceSize, duplicateCount)\n\texpectedTraceID := expected.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\n\tactual := ptrace.NewTraces()\n\tfound := s.waitForCondition(t, func(_ *testing.T) bool {\n\t\titerTraces := s.TraceReader.GetTraces(context.Background(), tracestore.GetTraceParams{TraceID: expectedTraceID})\n\t\ttraces, err := jiter.CollectWithErrors(jptrace.AggregateTraces(iterTraces))\n\t\tif err != nil {\n\t\t\tt.Logf(\"Error loading trace: %v\", err)\n\t\t\treturn false\n\t\t}\n\t\tif len(traces) == 0 {\n\t\t\treturn false\n\t\t}\n\t\trequire.Len(t, traces, 1)\n\t\tactual = traces[0]\n\t\treturn actual.SpanCount() >= expected.SpanCount()\n\t})\n\n\tt.Logf(\"%-23s Loaded trace, expected=%d, actual=%d\", time.Now().Format(\"2006-01-02 15:04:05.999\"), expected.SpanCount(), actual.SpanCount())\n\tif !assert.True(t, found, \"error loading trace, expected=%d, actual=%d\", expected.SpanCount(), actual.SpanCount()) {\n\t\tCompareTraces(t, expected, actual)\n\t\treturn\n\t}\n\n\tif validator != nil {\n\t\tvalidator(t, actual)\n\t}\n}\n\nfunc (s *StorageIntegration) testGetLargeTrace(t *testing.T) {\n\ts.helperTestGetTrace(t, 10008, 0, \"Large Trace over 10K without duplicates\", nil)\n}\n\nfunc (s *StorageIntegration) testGetTraceWithDuplicates(t *testing.T) {\n\tvalidator := func(t *testing.T, actual ptrace.Traces) {\n\t\tduplicateCount := 0\n\t\tseenIDs := make(map[pcommon.SpanID]int)\n\t\tfor _, span := range jptrace.SpanIter(actual) {\n\t\t\tseenIDs[span.SpanID()]++\n\t\t\tif seenIDs[span.SpanID()] > 1 {\n\t\t\t\tduplicateCount++\n\t\t\t}\n\t\t}\n\t\tassert.Positive(t, duplicateCount, \"Duplicate SpanIDs should be present in the trace\")\n\t}\n\ts.helperTestGetTrace(t, 200, 20, \"Trace with duplicate span IDs\", validator)\n}\n\nfunc (s *StorageIntegration) testGetOperations(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\n\tvar expected []tracestore.Operation\n\tif s.GetOperationsMissingSpanKind {\n\t\texpected = []tracestore.Operation{\n\t\t\t{Name: \"example-operation-1\"},\n\t\t\t{Name: \"example-operation-3\"},\n\t\t\t{Name: \"example-operation-4\"},\n\t\t}\n\t} else {\n\t\texpected = []tracestore.Operation{\n\t\t\t{Name: \"example-operation-1\", SpanKind: \"\"},\n\t\t\t{Name: \"example-operation-3\", SpanKind: \"server\"},\n\t\t\t{Name: \"example-operation-4\", SpanKind: \"client\"},\n\t\t}\n\t}\n\ts.loadParseAndWriteExampleTrace(t)\n\n\tvar actual []tracestore.Operation\n\tfound := s.waitForCondition(t, func(t *testing.T) bool {\n\t\tvar err error\n\t\tactual, err = s.TraceReader.GetOperations(context.Background(),\n\t\t\ttracestore.OperationQueryParams{ServiceName: \"example-service-1\"})\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\tsort.Slice(actual, func(i, j int) bool {\n\t\t\treturn actual[i].Name < actual[j].Name\n\t\t})\n\t\tt.Logf(\"Retrieved operations: %v\", actual)\n\t\treturn assert.ObjectsAreEqualValues(expected, actual)\n\t})\n\n\tif !assert.True(t, found) {\n\t\tt.Log(\"\\t Expected:\", expected)\n\t\tt.Log(\"\\t Actual  :\", actual)\n\t}\n}\n\nfunc (s *StorageIntegration) testGetTrace(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\n\texpected := s.loadParseAndWriteExampleTrace(t)\n\texpectedTraceID := expected.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\n\tactual := ptrace.Traces{} // no spans\n\tfound := s.waitForCondition(t, func(t *testing.T) bool {\n\t\titerTraces := s.TraceReader.GetTraces(context.Background(), tracestore.GetTraceParams{TraceID: expectedTraceID})\n\t\ttraces, err := jiter.CollectWithErrors(jptrace.AggregateTraces(iterTraces))\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\tif len(traces) == 0 {\n\t\t\treturn false\n\t\t}\n\t\trequire.Len(t, traces, 1)\n\t\tactual = traces[0]\n\t\treturn actual.SpanCount() >= expected.SpanCount()\n\t})\n\tif !assert.True(t, found) {\n\t\tCompareTraces(t, expected, actual)\n\t}\n\n\tt.Run(\"NotFound error\", func(t *testing.T) {\n\t\tfakeTraceID := pcommon.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}\n\t\titerTraces := s.TraceReader.GetTraces(context.Background(), tracestore.GetTraceParams{TraceID: fakeTraceID})\n\t\ttraces, err := jiter.CollectWithErrors(jptrace.AggregateTraces(iterTraces))\n\t\trequire.NoError(t, err) // v2 TraceReader no longer returns an error for not found\n\t\tassert.Empty(t, traces)\n\t})\n}\n\nfunc (s *StorageIntegration) testFindTraces(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\n\t// Note: all cases include ServiceName + StartTime range\n\ts.Fixtures = append(s.Fixtures, LoadAndParseQueryTestCases(t, \"fixtures/queries.json\")...)\n\n\t// Each query test case only specifies matching traces, but does not provide counterexamples.\n\t// To improve coverage we get all possible traces and store all of them before running queries.\n\tallTraceFixtures := make(map[string]ptrace.Traces)\n\texpectedTracesPerTestCase := make([][]ptrace.Traces, 0, len(s.Fixtures))\n\tfor _, queryTestCase := range s.Fixtures {\n\t\tvar expected []ptrace.Traces\n\t\tfor _, traceFixture := range queryTestCase.ExpectedFixtures {\n\t\t\ttrace, ok := allTraceFixtures[traceFixture]\n\t\t\tif !ok {\n\t\t\t\ttrace = s.getTraceFixture(t, traceFixture)\n\t\t\t\ts.writeTrace(t, trace)\n\t\t\t\tallTraceFixtures[traceFixture] = trace\n\t\t\t}\n\t\t\texpected = append(expected, trace)\n\t\t}\n\t\texpectedTracesPerTestCase = append(expectedTracesPerTestCase, expected)\n\t}\n\tfor i, queryTestCase := range s.Fixtures {\n\t\tt.Run(queryTestCase.Caption, func(t *testing.T) {\n\t\t\ts.skipIfNeeded(t)\n\t\t\texpected := expectedTracesPerTestCase[i]\n\t\t\tactual := s.findTracesByQuery(t, queryTestCase.Query.ToTraceQueryParams(t), expected)\n\t\t\tCompareTraceSlices(t, expected, actual)\n\t\t})\n\t}\n}\n\nfunc (s *StorageIntegration) findTracesByQuery(t *testing.T, query *tracestore.TraceQueryParams, expected []ptrace.Traces) []ptrace.Traces {\n\tvar traces []ptrace.Traces\n\tfound := s.waitForCondition(t, func(t *testing.T) bool {\n\t\titerTraces := s.TraceReader.FindTraces(context.Background(), *query)\n\t\tvar err error\n\t\ttraces, err = jiter.CollectWithErrors(jptrace.AggregateTraces(iterTraces))\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\tif len(expected) != len(traces) {\n\t\t\tt.Logf(\"Expecting certain number of traces: expected: %d, actual: %d\", len(expected), len(traces))\n\t\t\treturn false\n\t\t}\n\n\t\tif spanCount(expected) != spanCount(traces) {\n\t\t\tt.Logf(\"Expecting certain number of spans: expected: %d, actual: %d\", spanCount(expected), spanCount(traces))\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\trequire.True(t, found)\n\treturn traces\n}\n\nfunc (s *StorageIntegration) writeTrace(t *testing.T, trace ptrace.Traces) {\n\tt.Logf(\"%-23s Writing trace with %d spans\", time.Now().Format(\"2006-01-02 15:04:05.999\"), trace.SpanCount())\n\tctx, cx := context.WithTimeout(context.Background(), 5*time.Minute)\n\tdefer cx()\n\terr := s.TraceWriter.WriteTraces(ctx, trace)\n\trequire.NoError(t, err, \"Not expecting error when writing trace to storage\")\n\n\tt.Logf(\"%-23s Finished writing trace with %d spans\", time.Now().Format(\"2006-01-02 15:04:05.999\"), trace.SpanCount())\n}\n\nfunc (s *StorageIntegration) loadParseAndWriteExampleTrace(t *testing.T) ptrace.Traces {\n\ttrace := s.getTraceFixture(t, \"example_trace\")\n\ts.writeTrace(t, trace)\n\treturn trace\n}\n\nfunc (s *StorageIntegration) writeLargeTraceWithDuplicateSpanIds(\n\tt *testing.T,\n\ttotalCount int,\n\tdupFreq int,\n) ptrace.Traces {\n\ttrace := s.getTraceFixture(t, \"example_trace\")\n\trepeatedResourceSpan := trace.ResourceSpans().At(0)\n\trepeatedScopeSpan := repeatedResourceSpan.ScopeSpans().At(0)\n\trepeatedSpans := repeatedScopeSpan.Spans()\n\trepeatedSpan := repeatedSpans.At(0)\n\tnewResourceSpan := ptrace.NewResourceSpans()\n\tnewScopeSpan := newResourceSpan.ScopeSpans().AppendEmpty()\n\trepeatedResourceSpan.Resource().CopyTo(newResourceSpan.Resource())\n\trepeatedScopeSpan.Scope().CopyTo(newScopeSpan.Scope())\n\tnewSpans := newScopeSpan.Spans()\n\tnewSpans.EnsureCapacity(totalCount)\n\tfor i := range totalCount {\n\t\tnewSpan := ptrace.NewSpan()\n\t\trepeatedSpan.CopyTo(newSpan)\n\t\tswitch {\n\t\tcase dupFreq > 0 && i > 0 && i%dupFreq == 0:\n\t\t\tnewSpan.SetSpanID(repeatedSpan.SpanID())\n\t\tdefault:\n\t\t\tvar spanId [8]byte\n\t\t\tbinary.BigEndian.PutUint64(spanId[:], uint64(i+1))\n\t\t\tnewSpan.SetSpanID(spanId)\n\t\t}\n\t\tnewSpan.SetStartTimestamp(pcommon.NewTimestampFromTime(newSpan.StartTimestamp().AsTime().Add(time.Second * time.Duration(i+1))))\n\t\tnewSpan.SetEndTimestamp(pcommon.NewTimestampFromTime(newSpan.EndTimestamp().AsTime().Add(time.Second * time.Duration(i+1))))\n\t\tnewSpan.CopyTo(newSpans.AppendEmpty())\n\t}\n\tnewTrace := ptrace.NewTraces()\n\tnewResourceSpan.CopyTo(newTrace.ResourceSpans().AppendEmpty())\n\ts.writeTrace(t, newTrace)\n\treturn newTrace\n}\n\nfunc (*StorageIntegration) getTraceFixture(t *testing.T, fixture string) ptrace.Traces {\n\tfileName := fmt.Sprintf(\"fixtures/traces/%s.json\", fixture)\n\treturn loadOTLPTrace(t, fileName)\n}\n\nfunc loadOTLPTrace(t *testing.T, fileName string) ptrace.Traces {\n\t// #nosec\n\tinStr, err := fixtures.ReadFile(fileName)\n\trequire.NoError(t, err, \"Not expecting error when loading fixture %s\", fileName)\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\ttd, err := unmarshaller.UnmarshalTraces(inStr)\n\trequire.NoError(t, err, \"Not expecting error when unmarshaling fixture %s\", fileName)\n\tcorrectTimeForTrace(td)\n\treturn td\n}\n\n// LoadAndParseQueryTestCases loads and parses query test cases\nfunc LoadAndParseQueryTestCases(t *testing.T, queriesFile string) []*QueryFixtures {\n\tvar queries []*QueryFixtures\n\tloadAndParseJSON(t, queriesFile, &queries)\n\treturn queries\n}\n\nfunc loadAndParseJSON(t *testing.T, path string, object any) {\n\t// #nosec\n\tinStr, err := fixtures.ReadFile(path)\n\trequire.NoError(t, err, \"Not expecting error when loading fixture %s\", path)\n\terr = json.Unmarshal(correctTime(inStr), object)\n\trequire.NoError(t, err, \"Not expecting error when unmarshaling fixture %s\", path)\n}\n\n// required, because we want to only query on recent traces, so we replace all the dates with recent dates.\nfunc correctTime(jsonData []byte) []byte {\n\tjsonString := string(jsonData)\n\tnow := time.Now().UTC()\n\tyesterday := now.AddDate(0, 0, -1).Format(\"2006-01-02\")\n\ttwoDaysAgo := now.AddDate(0, 0, -2).Format(\"2006-01-02\")\n\tretString := strings.ReplaceAll(jsonString, \"2017-01-26\", yesterday)\n\tretString = strings.ReplaceAll(retString, \"2017-01-25\", twoDaysAgo)\n\treturn []byte(retString)\n}\n\nfunc correctTimeForTrace(td ptrace.Traces) {\n\tnow := time.Now().UTC()\n\tnormalizer := newDateOffsetNormalizer(now)\n\tnormalizer.normalizeTrace(td)\n}\n\nfunc spanCount(traces []ptrace.Traces) int {\n\tvar count int\n\tfor _, trace := range traces {\n\t\tcount += trace.SpanCount()\n\t}\n\treturn count\n}\n\n// === DependencyStore Integration Tests ===\n\nfunc (s *StorageIntegration) testGetDependencies(t *testing.T) {\n\tif s.DependencyReader == nil || s.DependencyWriter == nil {\n\t\tt.Skip(\"Skipping GetDependencies test because dependency reader or writer is nil\")\n\t\treturn\n\t}\n\n\ts.skipIfNeeded(t)\n\tdefer s.cleanUp(t)\n\n\tsource := model.JaegerDependencyLinkSource\n\tif !s.GetDependenciesReturnsSource {\n\t\tsource = \"\"\n\t}\n\n\texpected := []model.DependencyLink{\n\t\t{\n\t\t\tParent:    \"hello\",\n\t\t\tChild:     \"world\",\n\t\t\tCallCount: uint64(1),\n\t\t\tSource:    source,\n\t\t},\n\t\t{\n\t\t\tParent:    \"world\",\n\t\t\tChild:     \"hello\",\n\t\t\tCallCount: uint64(3),\n\t\t\tSource:    source,\n\t\t},\n\t}\n\tstartTime := time.Now()\n\trequire.NoError(t, s.DependencyWriter.WriteDependencies(startTime, expected))\n\n\tvar actual []model.DependencyLink\n\tfound := s.waitForCondition(t, func(t *testing.T) bool {\n\t\tvar err error\n\n\t\tactual, err = s.DependencyReader.GetDependencies(\n\t\t\tcontext.Background(),\n\t\t\tdepstore.QueryParameters{\n\t\t\t\tStartTime: startTime,\n\t\t\t\tEndTime:   startTime.Add(time.Minute * 5),\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\tsort.Slice(actual, func(i, j int) bool {\n\t\t\treturn actual[i].Parent < actual[j].Parent\n\t\t})\n\t\treturn assert.ObjectsAreEqualValues(expected, actual)\n\t})\n\n\tif !assert.True(t, found) {\n\t\tt.Log(\"\\t Expected:\", expected)\n\t\tt.Log(\"\\t Actual  :\", actual)\n\t}\n}\n\n// === Sampling Store Integration Tests ===\n\nfunc (s *StorageIntegration) testGetThroughput(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tif s.SamplingStore == nil {\n\t\tt.Skip(\"Skipping GetThroughput test because sampling store is nil\")\n\t\treturn\n\t}\n\tdefer s.cleanUp(t)\n\tstart := time.Now()\n\n\ts.insertThroughput(t)\n\n\texpected := 2\n\tvar actual []*samplemodel.Throughput\n\t_ = s.waitForCondition(t, func(t *testing.T) bool {\n\t\tvar err error\n\t\tactual, err = s.SamplingStore.GetThroughput(start, start.Add(time.Second*time.Duration(10)))\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\treturn assert.ObjectsAreEqualValues(expected, len(actual))\n\t})\n\tassert.Len(t, actual, expected)\n}\n\nfunc (s *StorageIntegration) testGetLatestProbability(t *testing.T) {\n\ts.skipIfNeeded(t)\n\tif s.SamplingStore == nil {\n\t\tt.Skip(\"Skipping GetLatestProbability test because sampling store is nil\")\n\t\treturn\n\t}\n\tdefer s.cleanUp(t)\n\n\ts.SamplingStore.InsertProbabilitiesAndQPS(\"newhostname1\", samplemodel.ServiceOperationProbabilities{\"new-srv3\": {\"op\": 0.123}}, samplemodel.ServiceOperationQPS{\"new-srv2\": {\"op\": 11}})\n\ts.SamplingStore.InsertProbabilitiesAndQPS(\"dell11eg843d\", samplemodel.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}}, samplemodel.ServiceOperationQPS{\"new-srv\": {\"op\": 4}})\n\n\texpected := samplemodel.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}}\n\tvar actual samplemodel.ServiceOperationProbabilities\n\tfound := s.waitForCondition(t, func(t *testing.T) bool {\n\t\tvar err error\n\t\tactual, err = s.SamplingStore.GetLatestProbabilities()\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn false\n\t\t}\n\t\treturn assert.ObjectsAreEqualValues(expected, actual)\n\t})\n\tif !assert.True(t, found) {\n\t\tt.Log(\"\\t Expected:\", expected)\n\t\tt.Log(\"\\t Actual  :\", actual)\n\t}\n}\n\nfunc (s *StorageIntegration) insertThroughput(t *testing.T) {\n\tthroughputs := []*samplemodel.Throughput{\n\t\t{Service: \"my-svc\", Operation: \"op\"},\n\t\t{Service: \"our-svc\", Operation: \"op2\"},\n\t}\n\terr := s.SamplingStore.InsertThroughput(throughputs)\n\trequire.NoError(t, err)\n}\n\n// RunAll runs all integration tests\nfunc (s *StorageIntegration) RunAll(t *testing.T) {\n\ts.RunSpanStoreTests(t)\n\tt.Run(\"GetDependencies\", s.testGetDependencies)\n\tt.Run(\"GetThroughput\", s.testGetThroughput)\n\tt.Run(\"GetLatestProbability\", s.testGetLatestProbability)\n}\n\n// RunSpanStoreTests runs only span related integration tests\nfunc (s *StorageIntegration) RunSpanStoreTests(t *testing.T) {\n\tt.Run(\"GetServices\", s.testGetServices)\n\tt.Run(\"GetOperations\", s.testGetOperations)\n\tt.Run(\"GetTrace\", s.testGetTrace)\n\tt.Run(\"GetLargeTrace\", s.testGetLargeTrace)\n\tt.Run(\"GetTraceWithDuplicateSpans\", s.testGetTraceWithDuplicates)\n\tt.Run(\"FindTraces\", s.testFindTraces)\n}\n"
  },
  {
    "path": "internal/storage/integration/memstore_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype MemStorageIntegrationTestSuite struct {\n\tStorageIntegration\n\tlogger *zap.Logger\n}\n\nfunc (s *MemStorageIntegrationTestSuite) initialize(t *testing.T) {\n\ts.logger, _ = testutils.NewLogger()\n\ttelset := telemetry.NoopSettings()\n\ttelset.Logger = s.logger\n\n\tf, err := memory.NewFactory(memory.Configuration{MaxTraces: 10000}, telset)\n\trequire.NoError(t, err)\n\ttraceReader, err := f.CreateTraceReader()\n\trequire.NoError(t, err)\n\ttraceWriter, err := f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\n\ts.SamplingStore = memory.NewSamplingStore(2)\n\ts.TraceReader = traceReader\n\ts.TraceWriter = traceWriter\n\n\t// TODO DependencyWriter is not implemented in memory store\n\n\ts.CleanUp = s.initialize\n}\n\nfunc TestMemoryStorage(t *testing.T) {\n\tSkipUnlessEnv(t, \"memory\")\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnce(t)\n\t})\n\ts := &MemStorageIntegrationTestSuite{}\n\ts.initialize(t)\n\ts.RunAll(t)\n}\n"
  },
  {
    "path": "internal/storage/integration/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\tif os.Getenv(\"STORAGE\") == \"elasticsearch\" || os.Getenv(\"STORAGE\") == \"opensearch\" {\n\t\ttestutils.VerifyGoLeaksForES(m)\n\t} else {\n\t\ttestutils.VerifyGoLeaks(m)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/integration/remote_memory_storage.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/config/confignet\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/health/grpc_health_v1\"\n\n\t\"github.com/jaegertracing/jaeger/cmd/remote-storage/app\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/memory\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n\t\"github.com/jaegertracing/jaeger/ports\"\n)\n\ntype RemoteMemoryStorage struct {\n\tserver         *app.Server\n\tstorageFactory *memory.Factory\n}\n\nfunc StartNewRemoteMemoryStorage(t *testing.T, port int) *RemoteMemoryStorage {\n\tlogger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller()))\n\tgrpcCfg := configgrpc.ServerConfig{\n\t\tNetAddr: confignet.AddrConfig{\n\t\t\tEndpoint: ports.PortToHostPort(port),\n\t\t},\n\t}\n\ttm := tenancy.NewManager(&tenancy.Options{\n\t\tEnabled: false,\n\t})\n\n\tt.Logf(\"Starting in-process remote storage server on %s\", grpcCfg.NetAddr.Endpoint)\n\ttelset := telemetry.NoopSettings()\n\ttelset.Logger = logger\n\n\ttraceFactory, err := memory.NewFactory(\n\t\tmemory.Configuration{\n\t\t\tMaxTraces: 10000,\n\t\t},\n\t\ttelset,\n\t)\n\trequire.NoError(t, err)\n\n\tserver, err := app.NewServer(context.Background(), grpcCfg, traceFactory, traceFactory, tm, telset)\n\trequire.NoError(t, err)\n\trequire.NoError(t, server.Start(context.Background()))\n\n\tconn, err := grpc.NewClient(\n\t\tgrpcCfg.NetAddr.Endpoint,\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t)\n\trequire.NoError(t, err)\n\tdefer conn.Close()\n\thealthClient := grpc_health_v1.NewHealthClient(conn)\n\trequire.Eventually(t, func() bool {\n\t\treq := &grpc_health_v1.HealthCheckRequest{}\n\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second*1)\n\t\tdefer cancel()\n\t\tresp, err := healthClient.Check(ctx, req)\n\t\tif err != nil {\n\t\t\tt.Logf(\"remote storage server is not ready: err=%v\", err)\n\t\t\treturn false\n\t\t}\n\t\tt.Logf(\"remote storage server status: %v\", resp.Status)\n\t\treturn resp.GetStatus() == grpc_health_v1.HealthCheckResponse_SERVING\n\t}, 30*time.Second, time.Second, \"failed to ensure remote storage server is ready\")\n\n\treturn &RemoteMemoryStorage{\n\t\tserver:         server,\n\t\tstorageFactory: traceFactory,\n\t}\n}\n\nfunc (s *RemoteMemoryStorage) Close(t *testing.T) {\n\trequire.NoError(t, s.server.Close())\n}\n"
  },
  {
    "path": "internal/storage/integration/trace_compare.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/davecgh/go-spew/spew\"\n\t\"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/ptracetest\"\n\t\"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil\"\n\t\"github.com/pmezard/go-difflib/difflib\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n)\n\n// CompareTraceSlices compares two trace slices\nfunc CompareTraceSlices(t *testing.T, expected []ptrace.Traces, actual []ptrace.Traces) {\n\trequire.Len(t, actual, len(expected))\n\tsortTracesByTraceID(expected)\n\tsortTracesByTraceID(actual)\n\tfor i, trace := range actual {\n\t\tmakeTraceReadyForComparison(trace)\n\t\tmakeTraceReadyForComparison(expected[i])\n\t\tif err := ptracetest.CompareTraces(expected[i], trace); err != nil {\n\t\t\tt.Logf(\"Actual trace and expected traces are not equal at index %d: %v\", i, err)\n\t\t\tt.Log(getDiff(t, expected[i], trace))\n\t\t\tt.Fail()\n\t\t}\n\t}\n}\n\n// CompareTraces compares two traces\nfunc CompareTraces(t *testing.T, expected ptrace.Traces, actual ptrace.Traces) {\n\tmakeTraceReadyForComparison(expected)\n\tmakeTraceReadyForComparison(actual)\n\tif err := ptracetest.CompareTraces(expected, actual); err != nil {\n\t\tt.Logf(\"Actual trace and expected traces are not equal: %v\", err)\n\t\tt.Log(getDiff(t, expected, actual))\n\t\tt.Fail()\n\t}\n}\n\nfunc makeTraceReadyForComparison(td ptrace.Traces) {\n\tnormalizeTrace(td)\n\tsortTrace(td)\n\tdedupeSpans(td)\n}\n\n// spans may contain spans with the same SpanID. Remove duplicates\n// and keep the first one. Use a map to keep track of the spans we've seen.\nfunc dedupeSpans(trace ptrace.Traces) {\n\tseen := make(map[pcommon.SpanID]bool)\n\tnewSpans := ptrace.NewResourceSpansSlice()\n\tfor _, resourceSpan := range trace.ResourceSpans().All() {\n\t\tnewResourceSpan := newSpans.AppendEmpty()\n\t\tresourceSpan.Resource().CopyTo(newResourceSpan.Resource())\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tnewScopeSpan := newResourceSpan.ScopeSpans().AppendEmpty()\n\t\t\tscopeSpan.Scope().CopyTo(newScopeSpan.Scope())\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif !seen[span.SpanID()] {\n\t\t\t\t\tseen[span.SpanID()] = true\n\t\t\t\t\tspan.CopyTo(newScopeSpan.Spans().AppendEmpty())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tnewSpans.CopyTo(trace.ResourceSpans())\n}\n\n// sortTrace sorts the spans of a trace on the basis of resource,\n// scope, trace id, span id and start time of span. The limitation\n// of using sorting provided by ptracetest is: It can't sort\n// those resource and scope spans which have same pcommon.Resource\n// and pcommon.InstrumentationScope but different ptrace.Span\nfunc sortTrace(td ptrace.Traces) {\n\tfor _, resourceSpan := range td.ResourceSpans().All() {\n\t\tsortAttributes(resourceSpan.Resource().Attributes())\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tsortAttributes(scopeSpan.Scope().Attributes())\n\t\t\tscopeSpan.Spans().Sort(func(a, b ptrace.Span) bool {\n\t\t\t\treturn compareSpans(a, b) < 0\n\t\t\t})\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tsortAttributes(span.Attributes())\n\t\t\t\tfor _, events := range span.Events().All() {\n\t\t\t\t\tsortAttributes(events.Attributes())\n\t\t\t\t}\n\t\t\t\tfor _, link := range span.Links().All() {\n\t\t\t\t\tsortAttributes(link.Attributes())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresourceSpan.ScopeSpans().Sort(func(a, b ptrace.ScopeSpans) bool {\n\t\t\treturn compareScopeSpans(a, b) < 0\n\t\t})\n\t}\n\ttd.ResourceSpans().Sort(compareResourceSpans)\n}\n\nfunc compareResourceSpans(a, b ptrace.ResourceSpans) bool {\n\tif lenComp := a.ScopeSpans().Len() - b.ScopeSpans().Len(); lenComp != 0 {\n\t\treturn lenComp < 0\n\t}\n\tif attrComp := compareAttributes(a.Resource().Attributes(), b.Resource().Attributes()); attrComp != 0 {\n\t\treturn attrComp < 0\n\t}\n\tfor i := 0; i < a.ScopeSpans().Len(); i++ {\n\t\taSpan := a.ScopeSpans().At(i)\n\t\tbSpan := b.ScopeSpans().At(i)\n\t\tif scopeComp := compareScopeSpans(aSpan, bSpan); scopeComp != 0 {\n\t\t\treturn scopeComp < 0\n\t\t}\n\t}\n\treturn false\n}\n\nfunc compareScopeSpans(a, b ptrace.ScopeSpans) int {\n\taScope := a.Scope()\n\tbScope := b.Scope()\n\tif nameComp := strings.Compare(aScope.Name(), bScope.Name()); nameComp != 0 {\n\t\treturn nameComp\n\t}\n\tif versionComp := strings.Compare(aScope.Version(), bScope.Version()); versionComp != 0 {\n\t\treturn versionComp\n\t}\n\tif attrComp := compareAttributes(aScope.Attributes(), bScope.Attributes()); attrComp != 0 {\n\t\treturn attrComp\n\t}\n\tif lenComp := a.Spans().Len() - b.Spans().Len(); lenComp != 0 {\n\t\treturn lenComp\n\t}\n\tfor i := 0; i < a.Spans().Len(); i++ {\n\t\taSpan := a.Spans().At(i)\n\t\tbSpan := b.Spans().At(i)\n\t\tif spanComp := compareSpans(aSpan, bSpan); spanComp != 0 {\n\t\t\treturn spanComp\n\t\t}\n\t}\n\treturn 0\n}\n\n// compareSpans compares two spans on the basis of trace id, span id and\n// start time. It should not be used to directly compare spans because it\n// leaves some top level fields like status, kind and attributes. In integration\n// tests it is used for sorting spans only, not for span comparison. For span\n// comparison, ptracetest.CompareTraces is used.\nfunc compareSpans(a, b ptrace.Span) int {\n\tif traceIdComp := compareTraceIDs(a.TraceID(), b.TraceID()); traceIdComp != 0 {\n\t\treturn traceIdComp\n\t}\n\tif spanIdComp := compareSpanIDs(a.SpanID(), b.SpanID()); spanIdComp != 0 {\n\t\treturn spanIdComp\n\t}\n\tif timeStampComp := compareTimestamps(a.StartTimestamp(), b.StartTimestamp()); timeStampComp != 0 {\n\t\treturn timeStampComp\n\t}\n\treturn 0\n}\n\nfunc compareTimestamps(a, b pcommon.Timestamp) int {\n\tif a == b {\n\t\treturn 0\n\t}\n\tif a > b {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\nfunc compareTraceIDs(a, b pcommon.TraceID) int {\n\treturn bytes.Compare(a[:], b[:])\n}\n\nfunc compareSpanIDs(a, b pcommon.SpanID) int {\n\treturn bytes.Compare(a[:], b[:])\n}\n\nfunc compareAttributes(a, b pcommon.Map) int {\n\taAttrs := pdatautil.MapHash(a)\n\tbAttrs := pdatautil.MapHash(b)\n\treturn bytes.Compare(aAttrs[:], bAttrs[:])\n}\n\nfunc sortTracesByTraceID(traces []ptrace.Traces) {\n\tsort.Slice(traces, func(i, j int) bool {\n\t\ta := traces[i].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\t\tb := traces[j].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\t\treturn compareTraceIDs(a, b) < 0\n\t})\n}\n\nfunc sortAttributes(attr pcommon.Map) {\n\tkeys := make([]string, 0, attr.Len())\n\tkeyVal := make(map[string]pcommon.Value, attr.Len())\n\tattr.Range(func(k string, v pcommon.Value) bool {\n\t\tkeys = append(keys, k)\n\t\tkeyVal[k] = v\n\t\treturn true\n\t})\n\tsort.Strings(keys)\n\tnewMap := pcommon.NewMap()\n\tfor _, k := range keys {\n\t\tval, _ := newMap.GetOrPutEmpty(k)\n\t\tkeyVal[k].CopyTo(val)\n\t}\n\tnewMap.CopyTo(attr)\n}\n\n// normalizeTrace assigns one resource span to one span. The fixtures\n// can have multiple spans under one resource/scope span but some\n// backends normalize traces for reducing complexity\n// (elasticsearch is one of the examples). The writer can\n// write traces without any normalization but reader will always\n// return normalized traces. Therefore, for comparing two spans\n// we need to normalize the expected fixtures.\nfunc normalizeTrace(td ptrace.Traces) {\n\tnormalizedResourceSpans := ptrace.NewResourceSpansSlice()\n\tnormalizedResourceSpans.EnsureCapacity(td.SpanCount())\n\tfor pos, span := range jptrace.SpanIter(td) {\n\t\tresource := pos.Resource.Resource()\n\t\tscope := pos.Scope.Scope()\n\t\tnormalizedResourceSpan := normalizedResourceSpans.AppendEmpty()\n\t\tresource.CopyTo(normalizedResourceSpan.Resource())\n\t\tnormalizedScopeSpan := normalizedResourceSpan.ScopeSpans().AppendEmpty()\n\t\tscope.CopyTo(normalizedScopeSpan.Scope())\n\t\tnormalizedSpan := normalizedScopeSpan.Spans().AppendEmpty()\n\t\tspan.CopyTo(normalizedSpan)\n\t}\n\tnormalizedResourceSpans.CopyTo(td.ResourceSpans())\n}\n\nfunc getDiff(t *testing.T, expected ptrace.Traces, actual ptrace.Traces) string {\n\tspewConfig := spew.ConfigState{\n\t\tIndent:                  \" \",\n\t\tDisablePointerAddresses: true,\n\t\tDisableCapacities:       true,\n\t\tSortKeys:                true,\n\t\tDisableMethods:          true,\n\t\tMaxDepth:                10,\n\t}\n\te := spewConfig.Sdump(expected)\n\ta := spewConfig.Sdump(actual)\n\tdiff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{\n\t\tA:        difflib.SplitLines(e),\n\t\tB:        difflib.SplitLines(a),\n\t\tFromFile: \"Expected\",\n\t\tFromDate: \"\",\n\t\tToFile:   \"Actual\",\n\t\tToDate:   \"\",\n\t\tContext:  1,\n\t})\n\trequire.NoError(t, err)\n\treturn \"\\n\\nDiff:\\n\" + diff\n}\n"
  },
  {
    "path": "internal/storage/integration/trace_compare_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage integration\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\nfunc TestDedupeSpans(t *testing.T) {\n\ttrace := ptrace.NewTraces()\n\tspans := trace.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\tspans.AppendEmpty().SetSpanID([8]byte{1})\n\tspans.AppendEmpty().SetSpanID([8]byte{1})\n\tspans.AppendEmpty().SetSpanID([8]byte{2})\n\tdedupeSpans(trace)\n\tassert.Equal(t, 2, trace.SpanCount())\n}\n"
  },
  {
    "path": "internal/storage/metricstore/disabled/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage disabled\n"
  },
  {
    "path": "internal/storage/metricstore/disabled/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage disabled\n"
  },
  {
    "path": "internal/storage/metricstore/disabled/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage disabled\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/disabled/reader.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage disabled\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\ntype (\n\t// MetricsReader represents a \"disabled\" metricstore.Reader implementation where\n\t// the METRICS_STORAGE_TYPE has not been set.\n\tMetricsReader struct{}\n\n\t// errMetricsQueryDisabledError is the error returned by disabledMetricsQueryService.\n\terrMetricsQueryDisabledError struct{}\n)\n\n// ErrDisabled is the error returned by a \"disabled\" MetricsQueryService on all of its endpoints.\nvar ErrDisabled = &errMetricsQueryDisabledError{}\n\nfunc (*errMetricsQueryDisabledError) Error() string {\n\treturn \"metrics querying is currently disabled\"\n}\n\n// NewMetricsReader returns a new Disabled MetricsReader.\nfunc NewMetricsReader() (*MetricsReader, error) {\n\treturn &MetricsReader{}, nil\n}\n\n// GetLatencies gets the latency metrics for the given set of latency query parameters.\nfunc (*MetricsReader) GetLatencies(context.Context, *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error) {\n\treturn nil, ErrDisabled\n}\n\n// GetCallRates gets the call rate metrics for the given set of call rate query parameters.\nfunc (*MetricsReader) GetCallRates(context.Context, *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error) {\n\treturn nil, ErrDisabled\n}\n\n// GetErrorRates gets the error rate metrics for the given set of error rate query parameters.\nfunc (*MetricsReader) GetErrorRates(context.Context, *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error) {\n\treturn nil, ErrDisabled\n}\n\n// GetMinStepDuration gets the minimum step duration (the smallest possible duration between two data points in a time series) supported.\nfunc (*MetricsReader) GetMinStepDuration(context.Context, *metricstore.MinStepDurationQueryParameters) (time.Duration, error) {\n\treturn 0, ErrDisabled\n}\n"
  },
  {
    "path": "internal/storage/metricstore/disabled/reader_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage disabled\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\nfunc TestGetLatencies(t *testing.T) {\n\treader, err := NewMetricsReader()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, reader)\n\n\tqParams := &metricstore.LatenciesQueryParameters{}\n\tr, err := reader.GetLatencies(context.Background(), qParams)\n\tassert.Zero(t, r)\n\trequire.ErrorIs(t, err, ErrDisabled)\n\trequire.EqualError(t, err, ErrDisabled.Error())\n}\n\nfunc TestGetCallRates(t *testing.T) {\n\treader, err := NewMetricsReader()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, reader)\n\n\tqParams := &metricstore.CallRateQueryParameters{}\n\tr, err := reader.GetCallRates(context.Background(), qParams)\n\tassert.Zero(t, r)\n\trequire.ErrorIs(t, err, ErrDisabled)\n\trequire.EqualError(t, err, ErrDisabled.Error())\n}\n\nfunc TestGetErrorRates(t *testing.T) {\n\treader, err := NewMetricsReader()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, reader)\n\n\tqParams := &metricstore.ErrorRateQueryParameters{}\n\tr, err := reader.GetErrorRates(context.Background(), qParams)\n\tassert.Zero(t, r)\n\trequire.ErrorIs(t, err, ErrDisabled)\n\trequire.EqualError(t, err, ErrDisabled.Error())\n}\n\nfunc TestGetMinStepDurations(t *testing.T) {\n\treader, err := NewMetricsReader()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, reader)\n\n\tqParams := &metricstore.MinStepDurationQueryParameters{}\n\tr, err := reader.GetMinStepDuration(context.Background(), qParams)\n\tassert.Zero(t, r)\n\trequire.ErrorIs(t, err, ErrDisabled)\n\trequire.EqualError(t, err, ErrDisabled.Error())\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/README.md",
    "content": "## `getCallRate` Calculation Explained\n\nThe `getCallRate` method calculates the call rate (requests per second) for a service by querying span data stored in Elasticsearch. The process involves three key stages: filtering the relevant spans, performing a time-series aggregation to count requests, and post-processing the aggregated data to calculate the final rate.\n\nThis document breaks down each of these stages, referencing the corresponding parts of the Elasticsearch query and the Go implementation.\n\n-----\n\n### 1\\. Filter Query Part\n\nThe first step is to isolate the specific set of documents (spans) needed for the calculation. We use a `bool` query with a `filter` clause, which is efficient as it doesn't contribute to document scoring.\n\n**ES Query Reference:**\n\n```json\n\"query\": {\n  \"bool\": {\n    \"filter\": [\n      { \"terms\": { \"process.serviceName\": \"[${service}]\" } },\n      { \"terms\": { \"tag.span@kind\": \"[{server}]\" } },\n      {\n        \"range\": {\n          \"startTimeMillis\": {\n            \"gte\": \"now-6h\",\n            \"lte\": \"now\",\n            \"format\": \"epoch_millis\"\n          }\n        }\n      }\n    ]\n  }\n}\n```\n\n**Explanation:**\n\n* **`{ \"terms\": { \"process.serviceName\": \"[${service}]\" } }`**: This filter selects spans that belong to the specified service. This is the primary entity for which we are calculating the call rate.\n* **`{ \"terms\": { \"tag.span@kind\": \"server\" } }`**: This is a critical filter for correctly calculating the *incoming* call rate. By filtering for spans where `span.kind` is `server` (or other), we ensure that we are only counting spans that represent a server (or other) receiving a request. This prevents us from incorrectly counting outgoing calls made by the service.\n* **`{ \"range\": { \"startTimeMillis\": ... } }`**: This filter restricts the spans to a specific time window. The `getCallRate` implementation uses an extended time range (by adding a 10-minute lookback period via `extendedStartTimeMillis`). This is done to ensure that when we calculate the rate for the earliest time points in our requested window, we have sufficient historical data to compute a meaningful value.\n\n**Code Reference:**\n\nThis logic is constructed in the `buildQuery` method. The filters are progressively added to a `boolQuery`.\n\n-----\n\n### 2\\. Aggregation Query Part\n\nAfter filtering the spans, we need to aggregate them into a time series that we can use to calculate a rate. The query does not calculate the rate directly; instead, it prepares the data by creating a running total of requests over time.\n\nNote: The reference ES query in the prompt includes a `moving_fn` aggregation to calculate the rate within Elasticsearch. However, the `getCallRate` method in the provided Go code uses a different approach: it fetches the cumulative sum and calculates the rate in the application layer, as described in the Post-Processing section. The aggregation below reflects the logic in the Go code.\n\n**ES Query Reference (as implemented in `buildCallRateAggregations`):**\n\n```json\n\"aggs\": {\n  \"requests_per_bucket\": {\n    \"date_histogram\": {\n      \"field\": \"startTimeMillis\",\n      \"fixed_interval\": \"60s\",\n      \"min_doc_count\": 0,\n      \"extended_bounds\": {\n        \"min\": \"now-6h\",\n        \"max\": \"now\"\n      }\n    },\n    \"aggs\": {\n      \"cumulative_requests\": {\n        \"cumulative_sum\": { \"buckets_path\": \"_count\" }\n      }\n    }\n  }\n}\n```\n\n**Explanation:**\n\n1.  **`date_histogram`**: This aggregation is the foundation of our time series. It groups the filtered spans into time buckets of a `fixed_interval` (e.g., `60s`). For each bucket, it provides a count (`_count`) of the documents (i.e., server spans) that fall within that time interval.\n\n2.  **`cumulative_sum`**: This is a sub-aggregation that operates on the buckets created by the `date_histogram`. It calculates a running total of the document counts. For any given time bucket, its `cumulative_requests` value is the sum of all `_count`s from the very first bucket up to and including the current one.\n\n**Code Reference:**\n\nThis aggregation pipeline is constructed in the `buildCallRateAggregations` method.\n\n-----\n\n### 3\\. Post-Processing Part\n\nThe final step happens in the application layer, within the `getCallRateProcessMetrics` function. This function takes the time series of `(timestamp, cumulative_request_count)` pairs returned by Elasticsearch and transforms it into a series of call rates.\n\n**Explanation:**\n\nThe function implements a sliding window algorithm to calculate the rate. It iterates through each data point and, for each point, it calculates the average rate over a preceding \"lookback\" period.\n\nThe core calculation for each point in the time series is:\n\n$$\\text{rate} = \\frac{\\Delta \\text{Value}}{\\Delta \\text{Time}} = \\frac{\\text{lastVal} - \\text{firstVal}}{\\text{windowSizeSeconds}}$$\n\nWhere:\n\n* `lastVal`: The cumulative request count at the end of the sliding window (the current data point).\n* `firstVal`: The cumulative request count at the beginning of the sliding window.\n* `lastVal - firstVal`: The total number of new requests that occurred during the window.\n* `windowSizeSeconds`: The duration of the sliding window in seconds.\n\n**Why this approach?**\n\nThis post-processing logic effectively calculates the slope of the cumulative requests graph over a sliding window, which is the definition of a rate. Performing this calculation client-side provides several advantages:\n\n* **Flexibility:** It gives full control over the rate calculation logic and how to handle edge cases, such as intervals with no data (`NaN` values).\n* **Simplicity:** It keeps the Elasticsearch query relatively simple and offloads potentially complex scripting from the database, which can be more performant and easier to maintain.\n* **Clarity:** The logic is explicitly defined in the Go code, making it clear how the final metric is derived from the raw cumulative counts.\n\n**Code Reference:**\n\nThe post-processing logic resides in `getCallRateProcessMetrics`, which is passed as a function pointer to the main query executor in `GetCallRates`.\n\n-----\n\n## `getLatencies` Calculation Explained\n\nThe `getLatencies` method retrieves latency metrics (specifically, a specified percentile of duration) for spans. Similar to `getCallRate`, it involves filtering spans, aggregating their durations into time series percentiles, and then post-processing the results.\n\n-----\n\n### 1\\. Filter Query Part\n\nThe filtering for `getLatencies` is identical to `getCallRate`, ensuring that only relevant spans within a specified time range and for specific services/span kinds are considered.\n\n-----\n\n### 2\\. Aggregation Query Part\n\nThe aggregation part for latencies involves grouping spans into time buckets and then calculating percentiles of the `duration` field within each bucket. This is the core calculation in our result.\n\n**ES Query Reference (as implemented in `buildLatenciesAggregations`):**\n\n```json\n\"aggs\": {\n  \"results_buckets\": {\n    \"date_histogram\": {\n      \"field\": \"startTimeMillis\",\n      \"fixed_interval\": \"60s\",\n      \"min_doc_count\": 0,\n      \"extended_bounds\": {\n        \"min\": \"now-6h\",\n        \"max\": \"now\"\n      }\n    },\n    \"aggs\": {\n      \"percentiles_of_bucket\": {\n        \"percentiles\": {\n          \"field\": \"duration\",\n          \"percents\": [95.0]\n        }\n      }\n    }\n  }\n}\n```\n\n**Explanation:**\n\n1.  **`date_histogram`**: This aggregation, similar to `getCallRate`, groups spans into fixed-interval time buckets based on their `startTimeMillis`. `MinDocCount(0)` ensures that even time buckets with no spans are returned, allowing for a complete time series.\n2.  **`percentiles`**: Nested within each date histogram bucket, this aggregation calculates the specified percentile (e.g., 95th) of the `duration` field for all spans within that bucket. The `duration` field typically represents the time taken for the span's operation in microseconds.\n\n**Code Reference:**\n\nThis aggregation pipeline is constructed in the `buildLatenciesAggregations` .\n\n-----\n\n### 3\\. Post-Processing Part\n\nThe `getLatenciesProcessMetrics` function takes the raw percentile values from Elasticsearch and performs further processing, primarily for scale down, round value and handling missing data.\n\n**Explanation:**\n\n* **Handling Missing Data**: Elasticsearch's percentiles aggregation returns `0.0` for time buckets with no documents. The code explicitly converts these `0.0` values to `math.NaN()` (Not a Number). This is crucial because `0.0` could be interpreted as a valid, albeit very fast, latency, whereas `NaN` correctly indicates an absence of data for that period.\n* **Post Processing**: The post-processing part is to scale down and round value got from `percentiles` ES aggregation.\n**Code Reference:**\n\nThe post-processing logic resides in `getLatenciesProcessMetrics`.\n\n-----\n\n### `getErrorRates` Calculation Explained\n\nThe `getErrorRates` method calculates the error rate for a service. This process leverages the same underlying mechanisms as `getCallRates` and `getLatencies` for data retrieval and aggregation, with an additional step to compute the ratio of errors to total calls.\n\n-----\n\n### 1\\. Filter Query Part\n\nFor `getErrorRates`, the initial filtering is designed to isolate spans that represent *errors*. This is achieved by extending the base filter with conditions that identify erroneous spans. i.e:\n\n```json\n  { \"term\": { \"tag.error\": true } },\n```\n\n-----\n\n### 2\\. Aggregation Query Part\n\nThe aggregation for error rates reuses the same `date_histogram` and `cumulative_sum` aggregation as `getCallRates`. This is because, fundamentally, an error rate requires counting the cumulative sum of errors over time, just as call rate counts cumulative calls.\n\n-----\n\n### 3\\. Post-Processing Part\n\nThe `getErrorRates` post-processing is the most distinct part, as it combines the results from two separate queries: one for **errors** and one for **total calls**.\n\n**Explanation:**\n\n1.  **Retrieve Error Counts:** First, the method executes a query to get the cumulative count of *error* spans, similar to how `getCallRates` obtains total call counts, but with the error-specific filter applied. The `ProcessCallRates` function is then used to convert these cumulative error counts into a time series of errors per second.\n2.  **Retrieve Total Call Counts:** `GetErrorRates` makes a separate call to `GetCallRates` to obtain the time series of total requests per second for the same service and time window.\n3.  **Calculate Error Rate:** The `calcErrorRates` function then takes these two time series (errors per second and calls per second) and, for each corresponding timestamp, divides the error rate by the call rate to compute the final error rate.\n\nThe core calculation for each point in the time series is:\n\n$$\\text{Error Rate} = \\frac{\\text{Errors rate}}{\\text{Calls rate}}$$\n\n**Code Reference:**\n\nThe post-processing logic resides in `ProcessErrorRates`, `calcErrorRates`, and `calculateErrorRateValue` functions."
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore/metricstoremetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\ntype Factory struct {\n\tconfig config.Configuration\n\ttelset telemetry.Settings\n\tclient es.Client\n}\n\n// NewFactory creates a new Factory with the given configuration and telemetry settings.\nfunc NewFactory(\n\tctx context.Context,\n\tcfg config.Configuration,\n\ttelset telemetry.Settings,\n\thttpAuth extensionauth.HTTPClient,\n) (*Factory, error) {\n\tif err := cfg.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tclient, err := config.NewClient(ctx, &cfg, telset.Logger, telset.Metrics, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Factory{\n\t\tconfig: cfg,\n\t\ttelset: telset,\n\t\tclient: client,\n\t}, nil\n}\n\n// CreateMetricsReader implements storage.MetricStoreFactory.\nfunc (f *Factory) CreateMetricsReader() (metricstore.Reader, error) {\n\tmr := NewMetricsReader(f.client, f.config, f.telset.Logger, f.telset.TracerProvider)\n\treturn metricstoremetrics.NewReaderDecorator(mr, f.telset.Metrics), nil\n}\n\nfunc (f *Factory) Close() error {\n\treturn f.client.Close()\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nvar _ storage.MetricStoreFactory = new(Factory)\n\n// mockESServerResponse simulates a successful Elasticsearch version response.\nvar mockESServerResponse = []byte(`\n{\n    \"version\": {\n       \"number\": \"6.8.0\"\n    }\n}\n`)\n\n// setupMockServer creates a mock HTTP server with the specified response and status code.\nfunc setupMockServer(t *testing.T, response []byte, statusCode int) *httptest.Server {\n\tmockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(statusCode)\n\t\tw.Write(response)\n\t}))\n\trequire.NotNil(t, mockServer)\n\tt.Cleanup(mockServer.Close)\n\n\treturn mockServer\n}\n\n// newTestFactoryConfig creates a default configuration for testing.\nfunc newTestFactoryConfig(serverURL string) config.Configuration {\n\treturn config.Configuration{\n\t\tServers:  []string{serverURL},\n\t\tLogLevel: \"debug\",\n\t}\n}\n\nfunc TestCreateMetricsReader(t *testing.T) {\n\tserver := setupMockServer(t, mockESServerResponse, http.StatusOK)\n\tcfg := newTestFactoryConfig(server.URL)\n\tf, err := NewFactory(context.Background(), cfg, telemetry.NoopSettings(), nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, f)\n\tdefer require.NoError(t, f.Close())\n\n\treader, err := f.CreateMetricsReader()\n\trequire.NoError(t, err)\n\tassert.NotNil(t, reader)\n}\n\nfunc TestNewFactory(t *testing.T) {\n\tmockServer := setupMockServer(t, mockESServerResponse, http.StatusOK)\n\ttests := []struct {\n\t\tname        string\n\t\tcfg         config.Configuration\n\t\tresponse    []byte\n\t\tstatusCode  int\n\t\texpectedErr bool\n\t}{\n\t\t{\n\t\t\tname:        \"valid config\",\n\t\t\tcfg:         newTestFactoryConfig(mockServer.URL),\n\t\t\texpectedErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid config - no servers\",\n\t\t\tcfg: config.Configuration{\n\t\t\t\tServers: []string{},\n\t\t\t},\n\t\t\texpectedErr: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid config - malformed server URL\",\n\t\t\tcfg:         newTestFactoryConfig(\"://malformed-url\"),\n\t\t\texpectedErr: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"ping failure for version detection\",          // New situation to test error from create es client\n\t\t\tcfg:         newTestFactoryConfig(\"http://localhost:9090\"), // Overridden by mock server\n\t\t\tresponse:    []byte(`{\"error\": \"internal server error\"}`),\n\t\t\tstatusCode:  http.StatusInternalServerError,\n\t\t\texpectedErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif tt.response != nil && tt.statusCode != 0 {\n\t\t\t\tserver := setupMockServer(t, tt.response, tt.statusCode)\n\t\t\t\ttt.cfg.Servers = []string{server.URL}\n\t\t\t\ttt.cfg.HealthCheckTimeoutStartup = 100 * time.Millisecond\n\t\t\t}\n\t\t\tf, err := NewFactory(context.Background(), tt.cfg, telemetry.NoopSettings(), nil)\n\n\t\t\tif tt.expectedErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Nil(t, f)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, f)\n\t\t\t\trequire.NoError(t, f.Close())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewFactoryWithAuthenticator(t *testing.T) {\n\tmockServer := setupMockServer(t, mockESServerResponse, http.StatusOK)\n\tcfg := newTestFactoryConfig(mockServer.URL)\n\n\tmockAuth := &mockHTTPAuthenticator{}\n\n\t// Test with authenticator\n\tf, err := NewFactory(context.Background(), cfg, telemetry.NoopSettings(), mockAuth)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, f)\n\tdefer require.NoError(t, f.Close())\n\n\treader, err := f.CreateMetricsReader()\n\trequire.NoError(t, err)\n\tassert.NotNil(t, reader)\n}\n\n// mockHTTPAuthenticator implements extensionauth.HTTPClient for testing\ntype mockHTTPAuthenticator struct{}\n\nfunc (*mockHTTPAuthenticator) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {\n\treturn &mockRoundTripper{base: base}, nil\n}\n\n// mockRoundTripper wraps the base RoundTripper\ntype mockRoundTripper struct {\n\tbase http.RoundTripper\n}\n\nfunc (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Set(\"Authorization\", \"Bearer mock-token\")\n\tif m.base != nil {\n\t\treturn m.base.RoundTrip(req)\n\t}\n\treturn &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/processor.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/gogo/protobuf/types\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\n// ScaleAndRoundLatencies processes raw latency metrics by applying scaling and rounding.\nfunc ScaleAndRoundLatencies(mf *metrics.MetricFamily) *metrics.MetricFamily {\n\tconst lookback = 1 // only current value\n\treturn applySlidingWindow(mf, lookback, scaleToMillisAndRound)\n}\n\n// CalculateCallRates processes raw call rate metrics by calculating rates and trimming to time range.\nfunc CalculateCallRates(mf *metrics.MetricFamily, params metricstore.BaseQueryParameters, timeRange TimeRange) *metrics.MetricFamily {\n\tprocessed := calcCallRate(mf, params)\n\treturn trimMetricPointsBefore(processed, timeRange.startTimeMillis)\n}\n\n// CalculateErrorRates processes error rate metrics by computing error rates from errors and calls.\nfunc CalculateErrorRates(rawErrors, calls *metrics.MetricFamily, params metricstore.BaseQueryParameters, timeRange TimeRange) *metrics.MetricFamily {\n\tprocessedErrors := CalculateCallRates(rawErrors, params, timeRange)\n\treturn calcErrorRates(processedErrors, calls)\n}\n\n// calcErrorRates computes error rates by dividing error metrics by call metrics.\nfunc calcErrorRates(errorMetrics, callMetrics *metrics.MetricFamily) *metrics.MetricFamily {\n\tresult := &metrics.MetricFamily{\n\t\tName:    errorMetrics.Name,\n\t\tType:    metrics.MetricType_GAUGE,\n\t\tHelp:    errorMetrics.Help,\n\t\tMetrics: make([]*metrics.Metric, 0, len(errorMetrics.Metrics)),\n\t}\n\n\t// Build a lookup map for error metrics by their labels.\n\terrorMetricsByLabels := make(map[string]*metrics.Metric)\n\tfor _, errorMetric := range errorMetrics.Metrics {\n\t\tlabelKey := getLabelKey(errorMetric.Labels)\n\t\terrorMetricsByLabels[labelKey] = errorMetric\n\t}\n\n\tfor _, callMetric := range callMetrics.Metrics {\n\t\tlabelKey := getLabelKey(callMetric.Labels)\n\t\terrorMetric, exists := errorMetricsByLabels[labelKey]\n\t\tif !exists {\n\t\t\t// If do not exist, it means that no data for error span, returning 0 error rate.\n\t\t\tmetricPoints := zeroValue(callMetric.MetricPoints)\n\t\t\tresult.Metrics = append(result.Metrics, &metrics.Metric{\n\t\t\t\tLabels:       callMetric.Labels,\n\t\t\t\tMetricPoints: metricPoints,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tmetricPoints := calculateErrorRatePoints(errorMetric.MetricPoints, callMetric.MetricPoints)\n\t\tresult.Metrics = append(result.Metrics, &metrics.Metric{\n\t\t\tLabels:       callMetric.Labels,\n\t\t\tMetricPoints: metricPoints,\n\t\t})\n\t}\n\n\treturn result\n}\n\n// zeroValue creates metric points with zero values matching the timestamps of the input points\nfunc zeroValue(points []*metrics.MetricPoint) []*metrics.MetricPoint {\n\tzeroPoints := make([]*metrics.MetricPoint, 0, len(points))\n\n\tfor _, point := range points {\n\t\tvalue := math.NaN()\n\t\t// Only append 0 for call_points value is not NaN\n\t\tif !math.IsNaN(point.GetGaugeValue().GetDoubleValue()) {\n\t\t\tvalue = 0.0\n\t\t}\n\t\tzeroPoints = append(zeroPoints, &metrics.MetricPoint{\n\t\t\tTimestamp: point.Timestamp,\n\t\t\tValue:     toDomainMetricPointValue(value),\n\t\t})\n\t}\n\n\treturn zeroPoints\n}\n\n// calculateErrorRatePoints computes error rates for corresponding metric points.\nfunc calculateErrorRatePoints(errorPoints, callPoints []*metrics.MetricPoint) []*metrics.MetricPoint {\n\tmetricPoints := make([]*metrics.MetricPoint, 0, len(errorPoints))\n\n\t// Build a lookup map for call points by timestamp.\n\tcallPointsByTime := make(map[int64]*metrics.MetricPoint)\n\tfor _, callPoint := range callPoints {\n\t\tkey, _ := timestampToKey(callPoint.Timestamp)\n\t\tcallPointsByTime[key] = callPoint\n\t}\n\n\tfor _, errorPoint := range errorPoints {\n\t\tkey, _ := timestampToKey(errorPoint.Timestamp)\n\t\tcallPoint, exists := callPointsByTime[key]\n\t\tif !exists {\n\t\t\tcontinue // Skip if no matching timestamp.\n\t\t}\n\n\t\tvalue := calculateErrorRateValue(errorPoint, callPoint)\n\t\tmetricPoints = append(metricPoints, &metrics.MetricPoint{\n\t\t\tTimestamp: errorPoint.Timestamp,\n\t\t\tValue:     toDomainMetricPointValue(value),\n\t\t})\n\t}\n\n\treturn metricPoints\n}\n\n// Helper function to generate a unique key for labels.\nfunc getLabelKey(labels []*metrics.Label) string {\n\t// Defensive copying\n\tlabelsCopy := make([]*metrics.Label, len(labels))\n\tcopy(labelsCopy, labels)\n\n\t// Sort by Name first, then by Value\n\tsort.Slice(labelsCopy, func(i, j int) bool {\n\t\tif labelsCopy[i].Name == labelsCopy[j].Name {\n\t\t\treturn labelsCopy[i].Value < labelsCopy[j].Value\n\t\t}\n\t\treturn labelsCopy[i].Name < labelsCopy[j].Name\n\t})\n\n\tkeys := make([]string, len(labelsCopy))\n\tfor i, label := range labelsCopy {\n\t\tkeys[i] = fmt.Sprintf(\"%s=%s\", label.Name, label.Value)\n\t}\n\treturn strings.Join(keys, \",\")\n}\n\n// Function to convert types.Timestamp to int64 (Unix nanoseconds)\nfunc timestampToKey(ts *types.Timestamp) (int64, error) {\n\tt, err := types.TimestampFromProto(ts)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn t.UnixNano(), nil\n}\n\n// calculateErrorRateValue computes the error rate for a single point.\nfunc calculateErrorRateValue(errorPoint, callPoint *metrics.MetricPoint) float64 {\n\terrorValue := errorPoint.GetGaugeValue().GetDoubleValue()\n\tcallValue := callPoint.GetGaugeValue().GetDoubleValue()\n\n\tif callValue == 0 {\n\t\treturn 0.0\n\t}\n\n\tif math.IsNaN(errorValue) && !math.IsNaN(callValue) {\n\t\treturn 0.0\n\t}\n\n\tif math.IsNaN(errorValue) || math.IsNaN(callValue) {\n\t\treturn math.NaN()\n\t}\n\treturn errorValue / callValue\n}\n\n// calcCallRate defines the rate calculation logic and pass in applySlidingWindow.\nfunc calcCallRate(mf *metrics.MetricFamily, params metricstore.BaseQueryParameters) *metrics.MetricFamily {\n\tlookback := int(math.Ceil(float64(params.RatePer.Milliseconds()) / float64(params.Step.Milliseconds())))\n\t// Ensure lookback >= 1\n\tlookback = int(math.Max(float64(lookback), 1))\n\n\twindowSizeSeconds := float64(lookback) * params.Step.Seconds()\n\n\tlastNonNaNMap := make(map[string]float64)\n\n\t// rateCalculator is a closure that captures 'lookback' and 'windowSizeSeconds'.\n\t// It implements the specific logic for calculating the rate.\n\trateCalculator := func(metric *metrics.Metric, window []*metrics.MetricPoint) float64 {\n\t\tlabelKey := getLabelKey(metric.Labels)\n\t\t// If the window is not \"full\" (i.e., we don't have enough preceding points\n\t\t// to calculate a rate over the full 'lookback' period), return NaN.\n\t\tif len(window) < lookback {\n\t\t\treturn math.NaN()\n\t\t}\n\n\t\tfirstValue := window[0].GetGaugeValue().GetDoubleValue()\n\t\tif math.IsNaN(firstValue) {\n\t\t\tfirstValue = lastNonNaNMap[labelKey] // Use the tracked value for this label set\n\t\t} else {\n\t\t\tlastNonNaNMap[labelKey] = firstValue // Update the tracked value\n\t\t}\n\n\t\tlastValue := window[len(window)-1].GetGaugeValue().GetDoubleValue()\n\t\t// If the current point (the last value in the window) is NaN, the rate cannot be defined.\n\t\t// Propagate NaN to indicate missing data for the result point.\n\t\tif math.IsNaN(lastValue) {\n\t\t\treturn math.NaN()\n\t\t}\n\n\t\trate := (lastValue - firstValue) / windowSizeSeconds\n\t\treturn math.Round(rate*100) / 100\n\t}\n\n\treturn applySlidingWindow(mf, lookback, rateCalculator)\n}\n\n// trimMetricPointsBefore removes metric points older than startMillis from each metric in the MetricFamily.\nfunc trimMetricPointsBefore(mf *metrics.MetricFamily, startMillis int64) *metrics.MetricFamily {\n\tfor _, metric := range mf.Metrics {\n\t\tpoints := metric.MetricPoints\n\t\t// Find first index where point >= startMillis\n\t\tcutoff := 0\n\t\tfor ; cutoff < len(points); cutoff++ {\n\t\t\tpoint := points[cutoff]\n\t\t\tpointMillis := point.Timestamp.Seconds*1000 + int64(point.Timestamp.Nanos)/1000000\n\t\t\tif pointMillis >= startMillis {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// Slice the array starting from cutoff index\n\t\tmetric.MetricPoints = points[cutoff:]\n\t}\n\treturn mf\n}\n\n// applySlidingWindow applies a given processing function over a moving window of metric points.\nfunc applySlidingWindow(mf *metrics.MetricFamily, lookback int, processor func(metric *metrics.Metric, window []*metrics.MetricPoint) float64) *metrics.MetricFamily {\n\tfor _, metric := range mf.Metrics {\n\t\tpoints := metric.MetricPoints\n\t\tif len(points) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tprocessedPoints := make([]*metrics.MetricPoint, 0, len(points))\n\n\t\tfor i, currentPoint := range points {\n\t\t\tstart := max(i-lookback+1, 0)\n\t\t\twindow := points[start : i+1]\n\t\t\tresultValue := processor(metric, window)\n\n\t\t\tprocessedPoints = append(processedPoints, &metrics.MetricPoint{\n\t\t\t\tTimestamp: currentPoint.Timestamp,\n\t\t\t\tValue:     toDomainMetricPointValue(resultValue),\n\t\t\t})\n\t\t}\n\t\tmetric.MetricPoints = processedPoints\n\t}\n\treturn mf\n}\n\nfunc scaleToMillisAndRound(_ *metrics.Metric, window []*metrics.MetricPoint) float64 {\n\tif len(window) == 0 {\n\t\treturn math.NaN()\n\t}\n\n\tv := window[len(window)-1].GetGaugeValue().GetDoubleValue()\n\t// Scale down the value (e.g., from microseconds to milliseconds)\n\tresultValue := v / 1000.0\n\treturn math.Round(resultValue*100) / 100 // Round to 2 decimal places\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/processor_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/types\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\n// TestProcessLatencies tests the ProcessLatencies function for scaling and handling edge cases.\nfunc TestProcessLatencies(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    *metrics.MetricFamily\n\t\texpected float64\n\t\tisNaN    bool\n\t}{\n\t\t{\n\t\t\tname: \"should scale microseconds to milliseconds\",\n\t\t\tinput: createMetricFamily(\"service_latencies\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(time.Now(), 1500.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\texpected: 1.5,\n\t\t\tisNaN:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"should handle NaN values\",\n\t\t\tinput: createMetricFamily(\"service_latencies\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(time.Now(), math.NaN()),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\tisNaN: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"should handle empty metrics\",\n\t\t\tinput: createMetricFamily(\"service_latencies\", []*metrics.Metric{}),\n\t\t\tisNaN: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := ScaleAndRoundLatencies(tt.input)\n\t\t\tif len(result.Metrics) == 0 || len(result.Metrics[0].MetricPoints) == 0 {\n\t\t\t\tassert.True(t, tt.isNaN)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvalue := result.Metrics[0].MetricPoints[0].GetGaugeValue().GetDoubleValue()\n\t\t\tif tt.isNaN {\n\t\t\t\tassert.True(t, math.IsNaN(value))\n\t\t\t} else {\n\t\t\t\tassert.InDelta(t, tt.expected, value, 0.1)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestProcessCallRates tests the ProcessCallRates function for rate calculation and trimming.\nfunc TestProcessCallRates(t *testing.T) {\n\tnow := time.Now()\n\tparams := metricstore.BaseQueryParameters{\n\t\tStep:    new(time.Second * 10),\n\t\tRatePer: new(time.Minute),\n\t}\n\ttimeRange := TimeRange{startTimeMillis: now.Add(-time.Minute).UnixMilli()}\n\n\ttests := []struct {\n\t\tname           string\n\t\tinput          *metrics.MetricFamily\n\t\texpectedPoints int\n\t\texpectedValue  float64\n\t\tisNaN          bool\n\t}{\n\t\t{\n\t\t\tname: \"should calculate call rates and trim points\",\n\t\t\tinput: createMetricFamily(\"service_call_rate\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(now.Add(-2*time.Minute), 10.0),\n\t\t\t\t\tcreateMetricPoint(now.Add(-time.Minute), 20.0),\n\t\t\t\t\tcreateMetricPoint(now, 30.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\texpectedPoints: 2,\n\t\t\tisNaN:          true,\n\t\t},\n\t\t{\n\t\t\tname: \"should handle insufficient window\",\n\t\t\tinput: createMetricFamily(\"service_call_rate\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(now, 10.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\texpectedPoints: 1,\n\t\t\tisNaN:          true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := CalculateCallRates(tt.input, params, timeRange)\n\t\t\tassert.Len(t, result.Metrics[0].MetricPoints, tt.expectedPoints)\n\t\t\tassert.True(t, math.IsNaN(result.Metrics[0].MetricPoints[0].GetGaugeValue().GetDoubleValue()))\n\t\t})\n\t}\n}\n\n// TestProcessErrorRates tests the ProcessErrorRates function for error rate calculation.\nfunc TestProcessErrorRates(t *testing.T) {\n\tnow := time.Now()\n\tparams := metricstore.BaseQueryParameters{\n\t\tStep:    new(time.Minute),\n\t\tRatePer: new(time.Minute),\n\t}\n\ttimeRange := TimeRange{startTimeMillis: now.Add(-time.Minute).UnixMilli()}\n\n\ttests := []struct {\n\t\tname         string\n\t\terrorMetrics *metrics.MetricFamily\n\t\tcallMetrics  *metrics.MetricFamily\n\t\texpected     float64\n\t\tisNaN        bool\n\t}{\n\t\t{\n\t\t\tname: \"should calculate error rates correctly\",\n\t\t\terrorMetrics: createMetricFamily(\"service_error_rate\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(now, 5.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\tcallMetrics: createMetricFamily(\"service_call_rate\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(now, 10.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\texpected: 0.0, // No rate during this time\n\t\t\tisNaN:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"should handle division by zero\",\n\t\t\terrorMetrics: createMetricFamily(\"service_error_rate\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(now, 5.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\tcallMetrics: createMetricFamily(\"service_call_rate\", []*metrics.Metric{\n\t\t\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\t\t\tcreateMetricPoint(now, 0.0),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\texpected: 0.0,\n\t\t\tisNaN:    false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := CalculateErrorRates(tt.errorMetrics, tt.callMetrics, params, timeRange)\n\t\t\tvalue := result.Metrics[0].MetricPoints[0].GetGaugeValue().GetDoubleValue()\n\t\t\tif tt.isNaN {\n\t\t\t\tassert.True(t, math.IsNaN(value))\n\t\t\t} else {\n\t\t\t\tassert.InDelta(t, tt.expected, value, 0.1)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCalculateErrorRateValue tests the calculateErrorRateValue function for edge cases.\nfunc TestCalculateErrorRateValue(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\terrorVal float64\n\t\tcallVal  float64\n\t\texpected float64\n\t\tisNaN    bool\n\t}{\n\t\t{\n\t\t\tname:     \"normal case\",\n\t\t\terrorVal: 5.0,\n\t\t\tcallVal:  10.0,\n\t\t\texpected: 0.5,\n\t\t\tisNaN:    false,\n\t\t},\n\t\t{\n\t\t\tname:     \"error NaN, call valid\",\n\t\t\terrorVal: math.NaN(),\n\t\t\tcallVal:  10.0,\n\t\t\texpected: 0.0,\n\t\t\tisNaN:    false,\n\t\t},\n\t\t{\n\t\t\tname:     \"error valid, call NaN\",\n\t\t\terrorVal: 5.0,\n\t\t\tcallVal:  math.NaN(),\n\t\t\tisNaN:    true,\n\t\t},\n\t\t{\n\t\t\tname:     \"both NaN\",\n\t\t\terrorVal: math.NaN(),\n\t\t\tcallVal:  math.NaN(),\n\t\t\tisNaN:    true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terrorPoint := createMetricPoint(time.Now(), tt.errorVal)\n\t\t\tcallPoint := createMetricPoint(time.Now(), tt.callVal)\n\t\t\tresult := calculateErrorRateValue(errorPoint, callPoint)\n\t\t\tif tt.isNaN {\n\t\t\t\tassert.True(t, math.IsNaN(result))\n\t\t\t} else {\n\t\t\t\tassert.InDelta(t, tt.expected, result, 0.1)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestTrimMetricPointsBefore tests the trimMetricPointsBefore function for trimming points.\nfunc TestTrimMetricPointsBefore(t *testing.T) {\n\tnow := time.Now()\n\tinput := createMetricFamily(\"test_metrics\", []*metrics.Metric{\n\t\tcreateMetric([]*metrics.MetricPoint{\n\t\t\tcreateMetricPoint(now.Add(-2*time.Minute), 10.0),\n\t\t\tcreateMetricPoint(now.Add(-time.Minute), 20.0),\n\t\t\tcreateMetricPoint(now, 30.0),\n\t\t}),\n\t})\n\n\tresult := trimMetricPointsBefore(input, now.Add(-90*time.Second).UnixMilli())\n\tassert.Len(t, result.Metrics[0].MetricPoints, 2)\n}\n\nfunc TestZeroValue(t *testing.T) {\n\t// Create test input with one NaN and one non-NaN value\n\tinput := []*metrics.MetricPoint{\n\t\t{Value: toDomainMetricPointValue(math.NaN())}, // NaN case\n\t\t{Value: toDomainMetricPointValue(42.0)},       // Non-NaN case\n\t}\n\n\tresult := zeroValue(input)\n\n\tassert.True(t, math.IsNaN(result[0].GetGaugeValue().GetDoubleValue()))\n\tassert.InDelta(t, 0.0, result[1].GetGaugeValue().GetDoubleValue(), 0.1)\n}\n\n// createMetricFamily creates a MetricFamily with the given name and metrics.\nfunc createMetricFamily(name string, m []*metrics.Metric) *metrics.MetricFamily {\n\treturn &metrics.MetricFamily{\n\t\tName:    name,\n\t\tType:    metrics.MetricType_GAUGE,\n\t\tHelp:    name + \" metrics\",\n\t\tMetrics: m,\n\t}\n}\n\n// createMetric creates a Metric with the given metric points.\nfunc createMetric(points []*metrics.MetricPoint) *metrics.Metric {\n\treturn &metrics.Metric{\n\t\tMetricPoints: points,\n\t}\n}\n\n// createMetricPoint creates a MetricPoint with the given timestamp and value.\nfunc createMetricPoint(ts time.Time, value float64) *metrics.MetricPoint {\n\ttimestamp, _ := types.TimestampProto(ts)\n\treturn &metrics.MetricPoint{\n\t\tTimestamp: timestamp,\n\t\tValue:     toDomainMetricPointValue(value),\n\t}\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/query_builder.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\tesquery \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n)\n\n// These constants define the specific names of aggregations used within Elasticsearch\n// queries. They are crucial for both constructing the query sent to Elasticsearch\n// and for correctly extracting the corresponding data from the Elasticsearch response.\nconst (\n\taggName            = \"results_buckets\"\n\tculmuAggName       = \"cumulative_requests\"\n\tpercentilesAggName = \"percentiles_of_bucket\"\n\tdateHistAggName    = \"date_histogram\"\n)\n\n// QueryBuilder is responsible for constructing Elasticsearch queries (bool and aggregation)\n// based on provided parameters and executing them to retrieve raw search results.\ntype QueryBuilder struct {\n\tclient           es.Client\n\tcfg              config.Configuration\n\ttimeRangeIndices spanstore.TimeRangeIndexFn\n}\n\n// NewQueryBuilder creates a new QueryBuilder instance.\nfunc NewQueryBuilder(client es.Client, cfg config.Configuration, logger *zap.Logger) *QueryBuilder {\n\treturn &QueryBuilder{\n\t\tclient: client,\n\t\tcfg:    cfg,\n\t\ttimeRangeIndices: spanstore.LoggingTimeRangeIndexFn(\n\t\t\tlogger,\n\t\t\tspanstore.TimeRangeIndicesFn(cfg.UseReadWriteAliases, cfg.ReadAliasSuffix, cfg.RemoteReadClusters),\n\t\t),\n\t}\n}\n\nfunc (q *QueryBuilder) BuildErrorBoolQuery(params metricstore.BaseQueryParameters, timeRange TimeRange) elastic.BoolQuery {\n\terrorQuery := elastic.NewTermQuery(\"tag.error\", true)\n\treturn q.BuildBoolQuery(params, timeRange, errorQuery)\n}\n\n// BuildBoolQuery constructs the base bool query for filtering metrics data.\nfunc (q *QueryBuilder) BuildBoolQuery(params metricstore.BaseQueryParameters, timeRange TimeRange, termsQueries ...elastic.Query) elastic.BoolQuery {\n\tboolQuery := elastic.NewBoolQuery()\n\n\tserviceNameQuery := elastic.NewTermsQuery(\"process.serviceName\", buildInterfaceSlice(params.ServiceNames)...)\n\tboolQuery.Filter(serviceNameQuery)\n\n\tspanKindField := strings.ReplaceAll(model.SpanKindKey, \".\", q.cfg.Tags.DotReplacement)\n\tspanKindQuery := elastic.NewTermsQuery(\"tag.\"+spanKindField, buildInterfaceSlice(normalizeSpanKinds(params.SpanKinds))...)\n\tboolQuery.Filter(spanKindQuery)\n\n\t// Add additional terms queries if provided\n\tfor _, termQuery := range termsQueries {\n\t\tboolQuery.Filter(termQuery)\n\t}\n\n\trangeQuery := esquery.NewRangeQuery(\"startTimeMillis\").\n\t\tGte(timeRange.extendedStartTimeMillis).\n\t\tLte(timeRange.endTimeMillis).\n\t\tFormat(\"epoch_millis\")\n\tboolQuery.Filter(rangeQuery)\n\n\treturn *boolQuery\n}\n\n// BuildLatenciesAggQuery constructs the aggregation query for latency metrics.\nfunc (q *QueryBuilder) BuildLatenciesAggQuery(params *metricstore.LatenciesQueryParameters, timeRange TimeRange) elastic.Aggregation {\n\tpercentilesAgg := elastic.NewPercentilesAggregation().\n\t\tField(\"duration\").\n\t\tPercentiles(params.Quantile * 100)\n\treturn q.buildTimeSeriesAggQuery(params.BaseQueryParameters, timeRange, percentilesAggName, percentilesAgg)\n}\n\n// BuildCallRateAggQuery constructs the aggregation query for call rate metrics.\nfunc (q *QueryBuilder) BuildCallRateAggQuery(params metricstore.BaseQueryParameters, timeRange TimeRange) elastic.Aggregation {\n\tcumulativeSumAgg := elastic.NewCumulativeSumAggregation().BucketsPath(\"_count\")\n\treturn q.buildTimeSeriesAggQuery(params, timeRange, culmuAggName, cumulativeSumAgg)\n}\n\n// buildTimeSeriesAggQuery constructs a time series aggregation with a sub-aggregation.\nfunc (*QueryBuilder) buildTimeSeriesAggQuery(params metricstore.BaseQueryParameters, timeRange TimeRange, subAggName string, subAgg elastic.Aggregation) elastic.Aggregation {\n\tfixedIntervalString := strconv.FormatInt(params.Step.Milliseconds(), 10) + \"ms\"\n\n\tdateHistAgg := elastic.NewDateHistogramAggregation().\n\t\tField(\"startTimeMillis\").\n\t\tFixedInterval(fixedIntervalString).\n\t\tMinDocCount(0).\n\t\tExtendedBounds(timeRange.extendedStartTimeMillis, timeRange.endTimeMillis).\n\t\tSubAggregation(subAggName, subAgg)\n\n\tif params.GroupByOperation {\n\t\treturn elastic.NewTermsAggregation().\n\t\t\tField(\"operationName\").\n\t\t\tSize(10).\n\t\t\tSubAggregation(dateHistAggName, dateHistAgg)\n\t}\n\treturn dateHistAgg\n}\n\n// Execute runs the Elasticsearch search with the provided bool and aggregation queries.\nfunc (q *QueryBuilder) Execute(ctx context.Context, boolQuery elastic.BoolQuery, aggQuery elastic.Aggregation, timeRange TimeRange) (*elastic.SearchResult, error) {\n\tindexName := q.cfg.Indices.IndexPrefix.Apply(\"jaeger-span-\")\n\tindices := q.timeRangeIndices(\n\t\tindexName,\n\t\tq.cfg.Indices.Services.DateLayout,\n\t\ttime.UnixMilli(timeRange.extendedStartTimeMillis).UTC(),\n\t\ttime.UnixMilli(timeRange.endTimeMillis).UTC(),\n\t\tconfig.RolloverFrequencyAsNegativeDuration(q.cfg.Indices.Services.RolloverFrequency),\n\t)\n\n\treturn q.client.Search(indices...).\n\t\tIgnoreUnavailable(true).\n\t\tQuery(&boolQuery).\n\t\tSize(0). // Set Size to 0 to return only aggregation results, excluding individual search hits\n\t\tAggregation(aggName, aggQuery).\n\t\tDo(ctx)\n}\n\n// normalizeSpanKinds normalizes a slice of span kinds.\nfunc normalizeSpanKinds(spanKinds []string) []string {\n\tnormalized := make([]string, len(spanKinds))\n\tfor i, kind := range spanKinds {\n\t\tnormalized[i] = strings.ToLower(strings.TrimPrefix(kind, \"SPAN_KIND_\"))\n\t}\n\treturn normalized\n}\n\n// buildInterfaceSlice converts []string to []interface{} for elastic terms query.\nfunc buildInterfaceSlice(s []string) []any {\n\tifaceSlice := make([]any, len(s))\n\tfor i, v := range s {\n\t\tifaceSlice[i] = v\n\t}\n\treturn ifaceSlice\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/query_builder_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\tesmetrics \"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\nvar commonTimeRange = TimeRange{\n\textendedStartTimeMillis: 1000,\n\tendTimeMillis:           2000,\n}\n\n// Test helper functions\nfunc setupTestQB() *QueryBuilder {\n\treturn NewQueryBuilder(nil, config.Configuration{Tags: config.TagsAsFields{DotReplacement: \"_\"}}, zap.NewNop())\n}\n\nfunc testAggregationStructure(t *testing.T, agg elastic.Aggregation, expectedInterval string, validateSubAggs func(map[string]any)) {\n\tsrc, err := agg.Source()\n\trequire.NoError(t, err)\n\n\taggMap, ok := src.(map[string]any)\n\trequire.True(t, ok)\n\n\tdateHist, ok := aggMap[\"date_histogram\"].(map[string]any)\n\trequire.True(t, ok)\n\trequire.Equal(t, expectedInterval, dateHist[\"fixed_interval\"])\n\n\tif validateSubAggs != nil {\n\t\tvalidateSubAggs(aggMap)\n\t}\n}\n\n// Tests\nfunc TestBuildBoolQuery(t *testing.T) {\n\tqb := setupTestQB()\n\tparams := metricstore.BaseQueryParameters{\n\t\tServiceNames: []string{\"service1\", \"service2\"},\n\t\tSpanKinds:    []string{\"client\", \"server\"},\n\t}\n\n\tboolQuery := qb.BuildBoolQuery(params, commonTimeRange)\n\trequire.NotNil(t, boolQuery)\n\n\tsrc, err := boolQuery.Source()\n\trequire.NoError(t, err)\n\n\tqueryMap := src.(map[string]any)\n\tboolClause := queryMap[\"bool\"].(map[string]any)\n\tfilterClause := boolClause[\"filter\"].([]any)\n\n\trequire.Len(t, filterClause, 3) // services, span kinds, time range\n}\n\nfunc TestBuildLatenciesAggregation(t *testing.T) {\n\tqb := setupTestQB()\n\tstep := time.Minute\n\tparams := &metricstore.LatenciesQueryParameters{\n\t\tBaseQueryParameters: metricstore.BaseQueryParameters{\n\t\t\tStep: &step,\n\t\t},\n\t\tQuantile: 0.95,\n\t}\n\n\tagg := qb.BuildLatenciesAggQuery(params, commonTimeRange)\n\trequire.NotNil(t, agg)\n\n\ttestAggregationStructure(t, agg, \"60000ms\", func(aggMap map[string]any) {\n\t\t_, ok := aggMap[\"aggregations\"].(map[string]any)\n\t\trequire.True(t, ok)\n\t})\n}\n\nfunc TestBuildCallRateAggregation(t *testing.T) {\n\tqb := setupTestQB()\n\tstep := time.Minute\n\tparams := metricstore.BaseQueryParameters{\n\t\tStep: &step,\n\t}\n\n\tagg := qb.BuildCallRateAggQuery(params, commonTimeRange)\n\trequire.NotNil(t, agg)\n\n\ttestAggregationStructure(t, agg, \"60000ms\", func(aggMap map[string]any) {\n\t\trequire.NotNil(t, aggMap[\"aggregations\"])\n\t})\n}\n\nfunc TestBuildTimeSeriesAggQuery(t *testing.T) {\n\tqb := setupTestQB()\n\tstep := time.Minute\n\tparams := metricstore.BaseQueryParameters{\n\t\tStep:             &step,\n\t\tGroupByOperation: false,\n\t}\n\tsubAgg := elastic.NewCumulativeSumAggregation()\n\n\tagg := qb.buildTimeSeriesAggQuery(params, commonTimeRange, \"test_sub_agg\", subAgg)\n\trequire.NotNil(t, agg)\n\n\ttestAggregationStructure(t, agg, \"60000ms\", func(aggMap map[string]any) {\n\t\taggs := aggMap[\"aggregations\"].(map[string]any)\n\t\trequire.NotNil(t, aggs[\"test_sub_agg\"])\n\t})\n}\n\nfunc TestExecute(t *testing.T) {\n\tmockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusOK)\n\t\tsendResponse(t, w, mockEsValidResponse)\n\t}))\n\tdefer mockServer.Close()\n\n\tcfg := &config.Configuration{\n\t\tIndices:  config.Indices{IndexPrefix: \"test-jaeger\"},\n\t\tServers:  []string{mockServer.URL},\n\t\tLogLevel: \"debug\",\n\t}\n\tclient := clientProvider(t, cfg, zap.NewNop(), esmetrics.NullFactory)\n\tqb := NewQueryBuilder(client, *cfg, zap.NewNop())\n\n\tboolQuery := elastic.NewBoolQuery()\n\taggQuery := elastic.NewDateHistogramAggregation().Field(\"startTimeMillis\").FixedInterval(\"60000ms\")\n\n\tresult, err := qb.Execute(context.Background(), *boolQuery, aggQuery, TimeRange{endTimeMillis: 0, startTimeMillis: 0})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, result)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/query_logger.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\n// QueryLogger handles logging and tracing of Elasticsearch queries.\ntype QueryLogger struct {\n\tlogger *zap.Logger\n\ttracer trace.Tracer\n}\n\n// NewQueryLogger creates a new QueryLogger.\nfunc NewQueryLogger(logger *zap.Logger, tracer trace.Tracer) *QueryLogger {\n\treturn &QueryLogger{\n\t\tlogger: logger,\n\t\ttracer: tracer,\n\t}\n}\n\n// TraceQuery adds tracing attributes.\nfunc (ql *QueryLogger) TraceQuery(ctx context.Context, metricName string) trace.Span {\n\t_, span := ql.tracer.Start(ctx, metricName)\n\tspan.SetAttributes(\n\t\totelsemconv.DBSystemAttribute(\"elasticsearch\"),\n\t\tattribute.Key(\"component\").String(\"es-metricsreader-query-logger\"),\n\t)\n\treturn span\n}\n\n// LogAndTraceResult logs the Elasticsearch query results and potentially adds them to the span.\nfunc (ql *QueryLogger) LogAndTraceResult(span trace.Span, searchResult *elastic.SearchResult) {\n\tif span.IsRecording() {\n\t\tresultJSON, _ := json.MarshalIndent(searchResult, \"\", \"  \")\n\t\tql.logger.Debug(\"Elasticsearch metricsreader query results\", zap.String(\"results\", string(resultJSON)))\n\t\tspan.SetAttributes(attribute.String(\"db.response_json\", string(resultJSON)))\n\t}\n}\n\n// LogErrorToSpan logs an error to the trace span.\nfunc (*QueryLogger) LogErrorToSpan(span trace.Span, err error) {\n\tspan.RecordError(err)\n\tspan.SetStatus(codes.Error, err.Error())\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/query_logger_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/sdk/trace/tracetest\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\ntype testContext struct {\n\tt        *testing.T\n\tlogger   *zap.Logger\n\ttp       trace.TracerProvider\n\texporter *tracetest.InMemoryExporter\n\ttracer   trace.Tracer\n\tql       *QueryLogger\n}\n\nfunc newTestContext(t *testing.T) *testContext {\n\tlogger := zaptest.NewLogger(t)\n\ttp, exporter := tracerProvider(t)\n\ttracer := tp.Tracer(\"test\")\n\tql := NewQueryLogger(logger, tracer)\n\n\treturn &testContext{\n\t\tt:        t,\n\t\tlogger:   logger,\n\t\ttp:       tp,\n\t\texporter: exporter,\n\t\ttracer:   tracer,\n\t\tql:       ql,\n\t}\n}\n\nfunc TestQueryLogger(t *testing.T) {\n\tt.Run(\"TraceQuery\", func(t *testing.T) {\n\t\ttc := newTestContext(t)\n\t\tassert.NotNil(t, tc.ql)\n\n\t\tspan := tc.ql.TraceQuery(context.Background(), \"test_query\")\n\t\tassert.NotNil(t, span)\n\n\t\t// End the span to ensure it gets exported\n\t\tspan.End()\n\n\t\t// Give the exporter time to process\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn len(tc.exporter.GetSpans()) > 0\n\t\t}, time.Second, 10*time.Millisecond)\n\n\t\tspans := tc.exporter.GetSpans()\n\t\tassert.Len(t, spans, 1)\n\t\tassert.Equal(t, \"test_query\", spans[0].Name)\n\t\tassert.Contains(t, spans[0].Attributes, otelsemconv.DBSystemAttribute(\"elasticsearch\"))\n\t})\n}\n\nfunc TestLogAndTraceResult(t *testing.T) {\n\tt.Run(\"LogAndTraceResult\", func(t *testing.T) {\n\t\ttc := newTestContext(t)\n\t\t_, span := tc.tracer.Start(context.Background(), \"test_span\")\n\n\t\tresult := &elastic.SearchResult{TookInMillis: 10, Hits: &elastic.SearchHits{TotalHits: &elastic.TotalHits{Value: 5, Relation: \"eq\"}}}\n\t\ttc.ql.LogAndTraceResult(span, result)\n\n\t\tspan.End()\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn len(tc.exporter.GetSpans()) > 0\n\t\t}, time.Second, 10*time.Millisecond)\n\n\t\tspans := tc.exporter.GetSpans()\n\t\tassert.Len(t, spans, 1)\n\t\tassert.Equal(t, \"test_span\", spans[0].Name)\n\t\tassert.Contains(t, spans[0].Attributes[0].Key, \"db.response_json\")\n\t})\n}\n\nfunc TestLogErrorToSpan(t *testing.T) {\n\tt.Run(\"LogErrorToSpan\", func(t *testing.T) {\n\t\ttc := newTestContext(t)\n\t\t_, span := tc.tracer.Start(context.Background(), \"test_span\")\n\n\t\ttestErr := errors.New(\"test error\")\n\t\ttc.ql.LogErrorToSpan(span, testErr)\n\n\t\tspan.End()\n\t\trequire.Eventually(t, func() bool {\n\t\t\treturn len(tc.exporter.GetSpans()) > 0\n\t\t}, time.Second, 10*time.Millisecond)\n\n\t\tspans := tc.exporter.GetSpans()\n\t\tassert.Len(t, spans, 1)\n\t\tassert.Equal(t, codes.Error, spans[0].Status.Code)\n\t\tassert.Equal(t, \"test error\", spans[0].Status.Description)\n\t})\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/reader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\nconst minStep = time.Millisecond\n\n// MetricsReader orchestrates metrics queries by:\n// 1. Calculating time ranges from query parameters.\n// 2. Delegating query construction and execution to Query.\n// 3. Using Translator to convert raw results to the domain model.\n// 4. Applying metric-specific processing to get desired metrics.\ntype MetricsReader struct {\n\tqueryLogger  *QueryLogger\n\tqueryBuilder *QueryBuilder\n}\n\n// TimeRange represents a time range for metrics queries.\ntype TimeRange struct {\n\tstartTimeMillis int64\n\tendTimeMillis   int64\n\t// extendedStartTimeMillis is an extended start time used for lookback periods\n\t// in certain aggregations (e.g., cumulative sums or rate calculations)\n\t// where data prior to startTimeMillis is needed to compute metrics accurately\n\t// within the primary time range. This typically accounts for a window of\n\t// preceding data (e.g., 10 minutes) to ensure that the initial data\n\t// points in the primary time range have enough historical context for calculation.\n\textendedStartTimeMillis int64\n}\n\n// MetricsQueryParams contains parameters for Elasticsearch metrics queries.\ntype MetricsQueryParams struct {\n\tmetricstore.BaseQueryParameters\n\tmetricName string\n\tmetricDesc string\n\tboolQuery  elastic.BoolQuery\n\taggQuery   elastic.Aggregation\n}\n\n// Pair represents a timestamp-value pair for metrics.\ntype Pair struct {\n\tTimeStamp int64\n\tValue     float64\n}\n\n// NewMetricsReader initializes a new MetricsReader.\nfunc NewMetricsReader(client es.Client, cfg config.Configuration, logger *zap.Logger, tracer trace.TracerProvider) *MetricsReader {\n\ttr := tracer.Tracer(\"elasticsearch-metricstore\")\n\treturn &MetricsReader{\n\t\tqueryLogger:  NewQueryLogger(logger, tr),\n\t\tqueryBuilder: NewQueryBuilder(client, cfg, logger),\n\t}\n}\n\n// GetLatencies retrieves latency metrics\nfunc (r MetricsReader) GetLatencies(ctx context.Context, params *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error) {\n\ttimeRange, err := calculateTimeRange(&params.BaseQueryParameters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmetricsParams := MetricsQueryParams{\n\t\tBaseQueryParameters: params.BaseQueryParameters,\n\t\tmetricName:          \"service_latencies\",\n\t\tmetricDesc:          fmt.Sprintf(\"%.2fth quantile latency, grouped by service\", params.Quantile),\n\t\tboolQuery:           r.queryBuilder.BuildBoolQuery(params.BaseQueryParameters, timeRange),\n\t\taggQuery:            r.queryBuilder.BuildLatenciesAggQuery(params, timeRange),\n\t}\n\n\tsearchResult, err := r.executeSearch(ctx, metricsParams, timeRange)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttranslator := NewTranslator(func(\n\t\tbuckets []*elastic.AggregationBucketHistogramItem,\n\t) []*Pair {\n\t\treturn bucketsToLatencies(buckets, params.Quantile*100)\n\t})\n\trawMetricFamily, err := translator.ToDomainMetricsFamily(metricsParams, searchResult)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Process the raw aggregation value to calculate latencies (ms)\n\treturn ScaleAndRoundLatencies(rawMetricFamily), nil\n}\n\n// GetCallRates retrieves call rate metrics\nfunc (r MetricsReader) GetCallRates(ctx context.Context, params *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error) {\n\ttimeRange, err := calculateTimeRange(&params.BaseQueryParameters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmetricsParams := MetricsQueryParams{\n\t\tBaseQueryParameters: params.BaseQueryParameters,\n\t\tmetricName:          \"service_call_rate\",\n\t\tmetricDesc:          \"calls/sec, grouped by service\",\n\t\tboolQuery:           r.queryBuilder.BuildBoolQuery(params.BaseQueryParameters, timeRange),\n\t\taggQuery:            r.queryBuilder.BuildCallRateAggQuery(params.BaseQueryParameters, timeRange),\n\t}\n\n\tsearchResult, err := r.executeSearch(ctx, metricsParams, timeRange)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Convert search results into raw metric family using translator\n\ttranslator := NewTranslator(bucketsToCallRate)\n\trawMetricFamily, err := translator.ToDomainMetricsFamily(metricsParams, searchResult)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn CalculateCallRates(rawMetricFamily, params.BaseQueryParameters, timeRange), nil\n}\n\n// GetErrorRates retrieves error rate metrics\nfunc (r MetricsReader) GetErrorRates(ctx context.Context, params *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error) {\n\ttimeRange, err := calculateTimeRange(&params.BaseQueryParameters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmetricsParams := MetricsQueryParams{\n\t\tBaseQueryParameters: params.BaseQueryParameters,\n\t\tmetricName:          \"service_error_rate\",\n\t\tmetricDesc:          \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\tboolQuery:           r.queryBuilder.BuildErrorBoolQuery(params.BaseQueryParameters, timeRange),\n\t\taggQuery:            r.queryBuilder.BuildCallRateAggQuery(params.BaseQueryParameters, timeRange), // Use the same aggQuery as GetCallRates\n\t}\n\n\tsearchResult, err := r.executeSearch(ctx, metricsParams, timeRange)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Convert search results into raw metric family using translator\n\ttranslator := NewTranslator(bucketsToCallRate)\n\trawErrorsMetrics, err := translator.ToDomainMetricsFamily(metricsParams, searchResult)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcallRateMetrics, err := r.GetCallRates(ctx, &metricstore.CallRateQueryParameters{BaseQueryParameters: params.BaseQueryParameters})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn CalculateErrorRates(rawErrorsMetrics, callRateMetrics, params.BaseQueryParameters, timeRange), nil\n}\n\n// GetMinStepDuration returns the minimum step duration.\nfunc (MetricsReader) GetMinStepDuration(_ context.Context, _ *metricstore.MinStepDurationQueryParameters) (time.Duration, error) {\n\treturn minStep, nil\n}\n\n// bucketsToPoints is a helper function for getting points value from ES AGG bucket\nfunc bucketsToPoints(buckets []*elastic.AggregationBucketHistogramItem, valueExtractor func(*elastic.AggregationBucketHistogramItem) float64) []*Pair {\n\tvar points []*Pair\n\n\tfor _, bucket := range buckets {\n\t\tvar value float64\n\t\t// If there is no data (doc_count = 0), we return NaN()\n\t\tif bucket.DocCount == 0 {\n\t\t\tvalue = math.NaN()\n\t\t} else {\n\t\t\t// Else extract the value and return it\n\t\t\tvalue = valueExtractor(bucket)\n\t\t}\n\n\t\tpoints = append(points, &Pair{\n\t\t\tTimeStamp: int64(bucket.Key),\n\t\t\tValue:     value,\n\t\t})\n\t}\n\treturn points\n}\n\nfunc bucketsToCallRate(buckets []*elastic.AggregationBucketHistogramItem) []*Pair {\n\tvalueExtractor := func(bucket *elastic.AggregationBucketHistogramItem) float64 {\n\t\taggMap, ok := bucket.Aggregations.CumulativeSum(culmuAggName)\n\t\tif !ok || aggMap.Value == nil {\n\t\t\treturn math.NaN()\n\t\t}\n\t\treturn *aggMap.Value\n\t}\n\treturn bucketsToPoints(buckets, valueExtractor)\n}\n\nfunc bucketsToLatencies(buckets []*elastic.AggregationBucketHistogramItem, percentileValue float64) []*Pair {\n\tvalueExtractor := func(bucket *elastic.AggregationBucketHistogramItem) float64 {\n\t\taggMap, ok := bucket.Aggregations.Percentiles(percentilesAggName)\n\t\tif !ok {\n\t\t\treturn math.NaN()\n\t\t}\n\t\tpercentileKey := fmt.Sprintf(\"%.1f\", percentileValue)\n\t\taggMapValue, ok := aggMap.Values[percentileKey]\n\t\tif !ok {\n\t\t\treturn math.NaN()\n\t\t}\n\t\treturn aggMapValue\n\t}\n\treturn bucketsToPoints(buckets, valueExtractor)\n}\n\n// executeSearch performs the Elasticsearch search.\nfunc (r MetricsReader) executeSearch(ctx context.Context, p MetricsQueryParams, timeRange TimeRange) (*elastic.SearchResult, error) {\n\tspan := r.queryLogger.TraceQuery(ctx, p.metricName)\n\tdefer span.End()\n\n\tsearchResult, err := r.queryBuilder.Execute(ctx, p.boolQuery, p.aggQuery, timeRange)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed executing metrics query: %w\", err)\n\t\tr.queryLogger.LogErrorToSpan(span, err)\n\t\treturn nil, err\n\t}\n\n\tr.queryLogger.LogAndTraceResult(span, searchResult)\n\n\t// Return raw search result\n\treturn searchResult, nil\n}\n\nfunc calculateTimeRange(params *metricstore.BaseQueryParameters) (TimeRange, error) {\n\tif params == nil || params.EndTime == nil || params.Lookback == nil {\n\t\treturn TimeRange{}, errors.New(\"invalid parameters\")\n\t}\n\tendTime := *params.EndTime\n\tstartTime := endTime.Add(-*params.Lookback)\n\textendedStartTime := startTime.Add(-10 * time.Minute)\n\n\treturn TimeRange{\n\t\tstartTimeMillis:         startTime.UnixMilli(),\n\t\tendTimeMillis:           endTime.UnixMilli(),\n\t\textendedStartTimeMillis: extendedStartTime.UnixMilli(),\n\t}, nil\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/reader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/sdk/trace/tracetest\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\tesmetrics \"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\nvar mockCallRateQuery = `{\n  \"query\": {\n    \"bool\": {\n      \"filter\": [\n        {\"terms\": {\"process.serviceName\": [\"driver\"]}},\n        {\"terms\": {\"tag.span@kind\": [\"server\"]}},\n        {\"range\": {\n          \"startTimeMillis\": {\n            \"gte\": 1749894300000,\n            \"lte\": 1749894960000,\n            \"format\": \"epoch_millis\"\n          }\n        }}\n      ]\n    }\n  },\n  \"size\": 0,\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"date_histogram\": {\n        \"field\": \"startTimeMillis\",\n        \"fixed_interval\": \"60000ms\",\n        \"min_doc_count\": 0,\n        \"extended_bounds\": {\n          \"min\": 1749894900000,\n          \"max\": 1749894960000\n        }\n      },\n      \"aggregations\": {\n        \"cumulative_requests\": {\n          \"cumulative_sum\": {\n            \"buckets_path\": \"_count\"}}}}}}\n`\n\nvar mockLatencyQuery = `\n{\n  \"size\": 0,\n  \"query\": {\n    \"bool\": {\n      \"filter\": [\n\t\t{\"terms\": {\"process.serviceName\": [\"driver\"]}},\n        {\"terms\": {\"tag.span@kind\": [\"server\"]}},\n        {\"range\": {\n            \"startTimeMillis\": {\n\t\t\t\t\"gte\": 1749894300000,\n\t\t\t\t\"lte\": 1749894960000,\n\t\t\t\t\"format\": \"epoch_millis\"\n\t\t\t}}}]}},\n  \"aggs\": {\n    \"requests_per_bucket\": {\n      \"date_histogram\": {\n        \"extended_bounds\": {\n          \"min\": 1749894900000,\n          \"max\": 1749894960000\n        },\n        \"field\": \"startTimeMillis\",\n        \"fixed_interval\": \"60000ms\",\n        \"min_doc_count\": 0\n      },\n      \"aggs\": {\n        \"percentiles_of_bucket\": {\n          \"percentiles\": {\n            \"field\": \"duration\",\n            \"percents\": [95]}}}}}}\n`\n\nvar mockErrorRateQuery = `{\n  \"query\": {\n    \"bool\": {\n      \"filter\": [\n        {\"terms\": {\"process.serviceName\": [\"driver\"]}},\n        {\"terms\": {\"tag.span@kind\": [\"server\"]}},\n\t\t{\"term\": {\"tag.error\": true}},\n        {\"range\": {\n          \"startTimeMillis\": {\n            \"gte\": 1749894300000,\n            \"lte\": 1749894960000,\n            \"format\": \"epoch_millis\"\n          }\n        }}\n      ]\n    }\n  },\n  \"size\": 0,\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"date_histogram\": {\n        \"field\": \"startTimeMillis\",\n        \"fixed_interval\": \"60000ms\",\n        \"min_doc_count\": 0,\n        \"extended_bounds\": {\n          \"min\": 1749894900000,\n          \"max\": 1749894960000\n        }\n      },\n      \"aggregations\": {\n        \"cumulative_requests\": {\n          \"cumulative_sum\": {\n            \"buckets_path\": \"_count\"}}}}}}\n`\n\nconst (\n\tmockEsValidResponse           = \"testdata/output_valid_es.json\"\n\tmockCallRateResponse          = \"testdata/output_call_rate.json\"\n\tmockCallRateOperationResponse = \"testdata/output_call_rate_operation.json\"\n\tmockEmptyResponse             = \"testdata/output_empty.json\"\n\tmockErrorResponse             = \"testdata/output_error_es.json\"\n\tmockLatencyResponse           = \"testdata/output_latencies.json\" // simple case\n\tmockLatencyOperationResponse  = \"testdata/output_latencies_operation.json\"\n\tmockErrorRateResponse         = \"testdata/output_errors_rate.json\"\n\tmockErrRateOperationResponse  = \"testdata/output_errors_rate_operation.json\"\n)\n\ntype metricsTestCase struct {\n\tname         string\n\tserviceNames []string\n\tspanKinds    []string\n\tgroupByOp    bool\n\tquery        string // Elasticsearch query to validate\n\tresponseFile string\n\twantName     string\n\twantDesc     string\n\twantLabels   []map[string]string\n\twantPoints   [][]struct {\n\t\tTimestampSec int64\n\t\tValue        float64\n\t}\n\twantErr string\n}\n\nfunc tracerProvider(t *testing.T) (trace.TracerProvider, *tracetest.InMemoryExporter) {\n\texporter := tracetest.NewInMemoryExporter()\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()),\n\t\tsdktrace.WithSyncer(exporter),\n\t)\n\tt.Cleanup(func() {\n\t\trequire.NoError(t, tp.ForceFlush(context.Background()))\n\t\trequire.NoError(t, tp.Shutdown(context.Background()))\n\t})\n\treturn tp, exporter\n}\n\nfunc clientProvider(t *testing.T, c *config.Configuration, logger *zap.Logger, metricsFactory esmetrics.Factory) es.Client {\n\tclient, err := config.NewClient(context.Background(), c, logger, metricsFactory, nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, client)\n\tt.Cleanup(func() {\n\t\trequire.NoError(t, client.Close())\n\t})\n\treturn client\n}\n\nfunc assertMetricFamily(t *testing.T, got *metrics.MetricFamily, m metricsTestCase) {\n\tif got == nil {\n\t\tt.Fatal(\"Expected non-nil MetricFamily\")\n\t}\n\tassert.Equal(t, m.wantName, got.Name, \"Metric name mismatch\")\n\tassert.Equal(t, m.wantDesc, got.Help, \"Metric description mismatch\")\n\tassert.Equal(t, metrics.MetricType_GAUGE, got.Type, \"Metric type mismatch\")\n\n\tfor i, metric := range got.Metrics {\n\t\tcurrWantLabels := m.wantLabels[i]\n\t\tgotLabels := make(map[string]string)\n\t\tfor _, label := range metric.Labels {\n\t\t\tgotLabels[label.Name] = label.Value\n\t\t}\n\t\tassert.Equal(t, currWantLabels, gotLabels, \"Labels mismatch\")\n\n\t\tif len(m.wantPoints) == 0 {\n\t\t\treturn\n\t\t}\n\t\tcurrWantPoints := m.wantPoints[i]\n\n\t\tif len(currWantPoints) == 0 {\n\t\t\tassert.Empty(t, metric.MetricPoints, \"Expected no metric points\")\n\t\t\treturn\n\t\t}\n\n\t\tassert.Len(t, metric.MetricPoints, len(currWantPoints), \"Metric points count mismatch\")\n\t\tfor j, point := range metric.MetricPoints {\n\t\t\tassert.Equal(t, currWantPoints[j].TimestampSec, point.Timestamp.GetSeconds(), \"Timestamp mismatch for point %d\", j)\n\t\t\tactualValue := point.Value.(*metrics.MetricPoint_GaugeValue).GaugeValue.GetDoubleValue()\n\t\t\tassert.InDelta(t, currWantPoints[j].Value, actualValue, 0.01, \"Value mismatch for point %d\", j)\n\t\t}\n\t}\n}\n\nfunc TestScaleToMillisAndRound_EmptyWindow(t *testing.T) {\n\tvar window []*metrics.MetricPoint\n\tresult := scaleToMillisAndRound(nil, window)\n\tassert.True(t, math.IsNaN(result))\n}\n\nfunc Test_ErrorCases(t *testing.T) {\n\tendTime := time.UnixMilli(0)\n\ttests := []struct {\n\t\tname    string\n\t\tparams  metricstore.BaseQueryParameters\n\t\twantErr string\n\t}{\n\t\t{\n\t\t\tname:    \"nil base params\",\n\t\t\twantErr: \"invalid parameters\",\n\t\t},\n\t\t{\n\t\t\tname:    \"nil end time params\",\n\t\t\tparams:  metricstore.BaseQueryParameters{},\n\t\t\twantErr: \"invalid parameters\",\n\t\t},\n\t\t{\n\t\t\tname: \"nil step params\",\n\t\t\tparams: metricstore.BaseQueryParameters{\n\t\t\t\tEndTime: &(endTime),\n\t\t\t},\n\t\t\twantErr: \"invalid parameters\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmockServer := startMockEsServer(t, \"\", mockEmptyResponse)\n\t\t\tdefer mockServer.Close()\n\t\t\treader, _ := setupMetricsReaderFromServer(t, mockServer)\n\t\t\tcallRateMetricFamily, err := reader.GetCallRates(context.Background(), &metricstore.CallRateQueryParameters{BaseQueryParameters: tc.params})\n\t\t\thelperAssertError(t, err, tc.wantErr, callRateMetricFamily)\n\t\t\tlatenciesMetricFamily, err := reader.GetLatencies(context.Background(), &metricstore.LatenciesQueryParameters{BaseQueryParameters: tc.params})\n\t\t\thelperAssertError(t, err, tc.wantErr, latenciesMetricFamily)\n\t\t\terrorMetricFamily, err := reader.GetErrorRates(context.Background(), &metricstore.ErrorRateQueryParameters{BaseQueryParameters: tc.params})\n\t\t\thelperAssertError(t, err, tc.wantErr, errorMetricFamily)\n\t\t})\n\t}\n}\n\nfunc helperAssertError(t *testing.T, err error, wantErr string, result *metrics.MetricFamily) {\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), wantErr)\n\trequire.Nil(t, result)\n}\n\nfunc TestGetCallRates(t *testing.T) {\n\texpectedPoints := [][]struct {\n\t\tTimestampSec int64\n\t\tValue        float64\n\t}{\n\t\t{\n\t\t\t{1749894840, math.NaN()},\n\t\t\t{1749894900, math.NaN()},\n\t\t\t{1749894960, math.NaN()},\n\t\t\t{1749895020, math.NaN()},\n\t\t\t{1749895080, math.NaN()},\n\t\t\t{1749895140, math.NaN()},\n\t\t\t{1749895200, math.NaN()},\n\t\t\t{1749895260, math.NaN()},\n\t\t\t{1749895320, math.NaN()},\n\t\t\t{1749895380, 0.75},\n\t\t\t{1749895440, 0.9},\n\t\t\t{1749895500, math.NaN()},\n\t\t},\n\t}\n\ttests := []metricsTestCase{\n\t\t{\n\t\t\tname:         \"group by service only\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tquery:        mockCallRateQuery,\n\t\t\tresponseFile: mockCallRateResponse,\n\t\t\twantName:     \"service_call_rate\",\n\t\t\twantDesc:     \"calls/sec, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: expectedPoints,\n\t\t},\n\t\t{\n\t\t\tname:         \"group by service and operation\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    true,\n\t\t\tresponseFile: mockCallRateOperationResponse,\n\t\t\twantName:     \"service_operation_call_rate\",\n\t\t\twantDesc:     \"calls/sec, grouped by service & operation\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\n\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\"operation\":    \"/FindCar\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\"operation\":    \"/FindDriverIDs\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\"operation\":    \"/FindNearest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantPoints: [][]struct {\n\t\t\t\tTimestampSec int64\n\t\t\t\tValue        float64\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t\t{1749894900, math.NaN()},\n\t\t\t\t\t{1749894960, math.NaN()},\n\t\t\t\t\t{1749895020, math.NaN()},\n\t\t\t\t\t{1749895080, math.NaN()},\n\t\t\t\t\t{1749895140, math.NaN()},\n\t\t\t\t\t{1749895200, math.NaN()},\n\t\t\t\t\t{1749895260, math.NaN()},\n\t\t\t\t\t{1749895320, math.NaN()},\n\t\t\t\t\t{1749895380, 0.75},\n\t\t\t\t\t{1749895440, 0.9},\n\t\t\t\t},\n\t\t\t\texpectedPoints[0],\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"different service names\",\n\t\t\tserviceNames: []string{\"jaeger\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\", \"SPAN_KIND_CLIENT\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: mockCallRateResponse,\n\t\t\twantName:     \"service_call_rate\",\n\t\t\twantDesc:     \"calls/sec, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"jaeger\"},\n\t\t\t},\n\t\t\twantPoints: expectedPoints,\n\t\t},\n\t\t{\n\t\t\tname:         \"empty response\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: mockEmptyResponse,\n\t\t\twantName:     \"service_call_rate\",\n\t\t\twantDesc:     \"calls/sec, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: nil,\n\t\t},\n\t\t{\n\t\t\tname:         \"server error\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: mockErrorResponse,\n\t\t\twantErr:      \"failed executing metrics query\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmockServer := startMockEsServer(t, tc.query, tc.responseFile)\n\t\t\tdefer mockServer.Close()\n\t\t\treader, exporter := setupMetricsReaderFromServer(t, mockServer)\n\n\t\t\tparams := &metricstore.CallRateQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParameters(tc),\n\t\t\t}\n\n\t\t\tmetricFamily, err := reader.GetCallRates(context.Background(), params)\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, tc.wantErr)\n\t\t\t\tassert.Nil(t, metricFamily)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassertMetricFamily(t, metricFamily, tc)\n\t\t\t}\n\n\t\t\tspans := exporter.GetSpans()\n\t\t\tif tc.wantErr == \"\" {\n\t\t\t\tassert.Len(t, spans, 1, \"Expected one span for the Elasticsearch query\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetLatencies(t *testing.T) {\n\ttests := []metricsTestCase{\n\t\t{\n\t\t\tname:         \"group by service only\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tquery:        mockLatencyQuery,\n\t\t\tresponseFile: mockLatencyResponse,\n\t\t\twantName:     \"service_latencies\",\n\t\t\twantDesc:     \"0.95th quantile latency, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: [][]struct {\n\t\t\t\tTimestampSec int64\n\t\t\t\tValue        float64\n\t\t\t}{{\n\t\t\t\t{1749894900, 0.2},\n\t\t\t\t{1749894960, 0.21},\n\t\t\t\t{1749895020, math.NaN()},\n\t\t\t}},\n\t\t},\n\t\t{\n\t\t\tname:         \"group by service and operation\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    true,\n\t\t\tresponseFile: mockLatencyOperationResponse,\n\t\t\twantName:     \"service_operation_latencies\",\n\t\t\twantDesc:     \"0.95th quantile latency, grouped by service & operation\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\n\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\"operation\":    \"/FindNearest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantPoints: [][]struct {\n\t\t\t\tTimestampSec int64\n\t\t\t\tValue        float64\n\t\t\t}{{\n\t\t\t\t{1749894900, 0.2},\n\t\t\t\t{1749894960, 0.21},\n\t\t\t}},\n\t\t},\n\t\t{\n\t\t\tname:         \"empty response\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: mockEmptyResponse,\n\t\t\twantName:     \"service_latencies\",\n\t\t\twantDesc:     \"0.95th quantile latency, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: nil,\n\t\t},\n\t\t{\n\t\t\tname:         \"server error\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: mockErrorResponse,\n\t\t\twantErr:      \"failed executing metrics query\",\n\t\t},\n\t\t{\n\t\t\tname:         \"convert error\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    true,\n\t\t\tresponseFile: \"testdata/output_error_latencies.json\",\n\t\t\twantErr:      \"failed to convert aggregations to metrics\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmockServer := startMockEsServer(t, tc.query, tc.responseFile)\n\t\t\tdefer mockServer.Close()\n\t\t\treader, exporter := setupMetricsReaderFromServer(t, mockServer)\n\n\t\t\tparams := &metricstore.LatenciesQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParameters(tc),\n\t\t\t\tQuantile:            0.95,\n\t\t\t}\n\n\t\t\tmetricFamily, err := reader.GetLatencies(context.Background(), params)\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, tc.wantErr)\n\t\t\t\tassert.Empty(t, metricFamily)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassertMetricFamily(t, metricFamily, tc)\n\t\t\t}\n\n\t\t\tspans := exporter.GetSpans()\n\t\t\tif tc.wantErr == \"\" {\n\t\t\t\tassert.Len(t, spans, 1, \"Expected one span for the Elasticsearch query\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetLatencies_WithDifferentQuantiles(t *testing.T) {\n\ttests := []metricsTestCase{\n\t\t{\n\t\t\tname:         \"0.5 quantile\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: \"testdata/output_latencies_50.json\",\n\t\t\twantName:     \"service_latencies\",\n\t\t\twantDesc:     \"0.50th quantile latency, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: [][]struct {\n\t\t\t\tTimestampSec int64\n\t\t\t\tValue        float64\n\t\t\t}{{\n\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t{1749894900, 0.15},\n\t\t\t\t{1749894960, 0.16},\n\t\t\t\t{1749895020, 0.17},\n\t\t\t\t{1749895080, 0.18},\n\t\t\t\t{1749895140, 0.19},\n\t\t\t\t{1749895200, math.NaN()},\n\t\t\t\t{1749895260, 0.2},\n\t\t\t\t{1749895320, 0.21},\n\t\t\t\t{1749895380, 0.22},\n\t\t\t\t{1749895440, 0.23},\n\t\t\t}},\n\t\t},\n\t\t{\n\t\t\tname:         \"0.75 quantile\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: \"testdata/output_latencies_75.json\",\n\t\t\twantName:     \"service_latencies\",\n\t\t\twantDesc:     \"0.75th quantile latency, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: [][]struct {\n\t\t\t\tTimestampSec int64\n\t\t\t\tValue        float64\n\t\t\t}{{\n\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t{1749894900, 0.25},\n\t\t\t\t{1749894960, 0.26},\n\t\t\t\t{1749895020, 0.27},\n\t\t\t\t{1749895080, 0.28},\n\t\t\t\t{1749895140, 0.29},\n\t\t\t\t{1749895200, math.NaN()},\n\t\t\t\t{1749895260, 0.3},\n\t\t\t\t{1749895320, 0.31},\n\t\t\t\t{1749895380, 0.32},\n\t\t\t\t{1749895440, 0.33},\n\t\t\t}},\n\t\t},\n\t\t{\n\t\t\tname:         \"0.95 quantile\",\n\t\t\tserviceNames: []string{\"driver\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOp:    false,\n\t\t\tresponseFile: \"testdata/output_latencies_95.json\",\n\t\t\twantName:     \"service_latencies\",\n\t\t\twantDesc:     \"0.95th quantile latency, grouped by service\",\n\t\t\twantLabels: []map[string]string{\n\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t},\n\t\t\twantPoints: [][]struct {\n\t\t\t\tTimestampSec int64\n\t\t\t\tValue        float64\n\t\t\t}{{\n\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t{1749894900, 0.45},\n\t\t\t\t{1749894960, 0.46},\n\t\t\t\t{1749895020, 0.47},\n\t\t\t\t{1749895080, 0.48},\n\t\t\t\t{1749895140, 0.49},\n\t\t\t\t{1749895200, math.NaN()},\n\t\t\t\t{1749895260, 0.50},\n\t\t\t\t{1749895320, 0.51},\n\t\t\t\t{1749895380, 0.52},\n\t\t\t\t{1749895440, 0.53},\n\t\t\t}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmockServer := startMockEsServer(t, \"\", tc.responseFile)\n\t\t\tdefer mockServer.Close()\n\t\t\treader, exporter := setupMetricsReaderFromServer(t, mockServer)\n\n\t\t\tparams := &metricstore.LatenciesQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParameters(tc),\n\t\t\t\tQuantile:            0.95, // Will be adjusted based on test case\n\t\t\t}\n\n\t\t\t// Set the correct quantile for each test case\n\t\t\tswitch tc.name {\n\t\t\tcase \"0.5 quantile\":\n\t\t\t\tparams.Quantile = 0.5\n\t\t\tcase \"0.75 quantile\":\n\t\t\t\tparams.Quantile = 0.75\n\t\t\tcase \"0.95 quantile\":\n\t\t\t\tparams.Quantile = 0.95\n\t\t\tdefault:\n\t\t\t\tt.Errorf(\"Unexpected test case name: %s\", tc.name)\n\t\t\t}\n\n\t\t\tmetricFamily, err := reader.GetLatencies(context.Background(), params)\n\t\t\trequire.NoError(t, err)\n\t\t\tassertMetricFamily(t, metricFamily, tc)\n\n\t\t\tspans := exporter.GetSpans()\n\t\t\tassert.Len(t, spans, 1, \"Expected one span for the Elasticsearch query\")\n\t\t})\n\t}\n}\n\nfunc TestGetLatenciesBucketsToPoints_ErrorCases(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tbuckets         []*elastic.AggregationBucketHistogramItem\n\t\tpercentileValue float64\n\t}{\n\t\t{\n\t\t\tname:            \"missing percentiles aggregation\",\n\t\t\tpercentileValue: 95.0,\n\t\t\tbuckets: []*elastic.AggregationBucketHistogramItem{\n\t\t\t\t{\n\t\t\t\t\tKey:          1749894900000,\n\t\t\t\t\tDocCount:     1,\n\t\t\t\t\tAggregations: map[string]json.RawMessage{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:            \"missing percentile key\",\n\t\t\tpercentileValue: 95.0,\n\t\t\tbuckets: []*elastic.AggregationBucketHistogramItem{\n\t\t\t\t{\n\t\t\t\t\tKey:      1749894900000,\n\t\t\t\t\tDocCount: 1,\n\t\t\t\t\tAggregations: map[string]json.RawMessage{\n\t\t\t\t\t\tpercentilesAggName: json.RawMessage(`{\"values\": {\"90.0\": 200.0}}`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"nil percentile value\",\n\t\t\tbuckets: []*elastic.AggregationBucketHistogramItem{\n\t\t\t\t{\n\t\t\t\t\tKey:      1749894900000,\n\t\t\t\t\tDocCount: 1,\n\t\t\t\t\tAggregations: map[string]json.RawMessage{\n\t\t\t\t\t\tpercentilesAggName: json.RawMessage(`{\"values\": {\"95.0\": null}}`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := bucketsToLatencies(tt.buckets, tt.percentileValue)\n\t\t\tassert.True(t, math.IsNaN(result[0].Value))\n\t\t})\n\t}\n}\n\nfunc TestGetErrorRates(t *testing.T) {\n\texpectedPoints := [][]struct {\n\t\tTimestampSec int64\n\t\tValue        float64\n\t}{\n\t\t{\n\t\t\t{1749894840, math.NaN()},\n\t\t\t{1749894900, math.NaN()},\n\t\t\t{1749894960, math.NaN()},\n\t\t\t{1749895020, math.NaN()},\n\t\t\t{1749895080, math.NaN()},\n\t\t\t{1749895140, math.NaN()},\n\t\t\t{1749895200, math.NaN()},\n\t\t\t{1749895260, math.NaN()},\n\t\t\t{1749895320, math.NaN()},\n\t\t\t{1749895380, 0.5},\n\t\t\t{1749895440, 0.75},\n\t\t\t{1749895500, math.NaN()},\n\t\t},\n\t}\n\n\ttests := []struct {\n\t\tmetricsTestCase\n\t\tcallRateFile string\n\t}{\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"group by service only - successful\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    false,\n\t\t\t\tquery:        mockErrorRateQuery,\n\t\t\t\tresponseFile: mockErrorRateResponse,\n\t\t\t\twantName:     \"service_error_rate\",\n\t\t\t\twantDesc:     \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\t\t\twantLabels: []map[string]string{\n\t\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t\t},\n\t\t\t\twantPoints: expectedPoints,\n\t\t\t},\n\t\t\tcallRateFile: mockCallRateResponse,\n\t\t},\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"group by service and operation - successful\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    true,\n\t\t\t\tresponseFile: mockErrRateOperationResponse,\n\t\t\t\twantName:     \"service_operation_error_rate\",\n\t\t\t\twantDesc:     \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service & operation\",\n\t\t\t\twantLabels: []map[string]string{\n\t\t\t\t\t{\n\t\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\t\"operation\":    \"/FindCar\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\t\"operation\":    \"/FindDriverIDs\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"service_name\": \"driver\",\n\t\t\t\t\t\t\"operation\":    \"/FindNearest\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\twantPoints: [][]struct {\n\t\t\t\t\tTimestampSec int64\n\t\t\t\t\tValue        float64\n\t\t\t\t}{\n\t\t\t\t\t{ // FindCar Expected Points\n\t\t\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t\t},\n\t\t\t\t\t{ // FindDriverIDS Expected Points\n\t\t\t\t\t\t{1749894840, math.NaN()},\n\t\t\t\t\t\t{1749894900, math.NaN()},\n\t\t\t\t\t\t{1749894960, math.NaN()},\n\t\t\t\t\t\t{1749895020, math.NaN()},\n\t\t\t\t\t\t{1749895080, math.NaN()},\n\t\t\t\t\t\t{1749895140, math.NaN()},\n\t\t\t\t\t\t{1749895200, math.NaN()},\n\t\t\t\t\t\t{1749895260, math.NaN()},\n\t\t\t\t\t\t{1749895320, math.NaN()},\n\t\t\t\t\t\t{1749895380, 0.8},\n\t\t\t\t\t\t{1749895440, 0.8},\n\t\t\t\t\t},\n\t\t\t\t\texpectedPoints[0], // FindNearest Expected Points\n\t\t\t\t},\n\t\t\t},\n\t\t\tcallRateFile: mockCallRateOperationResponse,\n\t\t},\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"empty error response\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    false,\n\t\t\t\tresponseFile: mockEmptyResponse,\n\t\t\t\twantName:     \"service_error_rate\",\n\t\t\t\twantDesc:     \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\t\t\twantLabels: []map[string]string{\n\t\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t\t},\n\t\t\t\twantPoints: nil,\n\t\t\t},\n\t\t\tcallRateFile: mockCallRateResponse,\n\t\t},\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"empty call rate response\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    false,\n\t\t\t\tresponseFile: mockErrorRateResponse,\n\t\t\t\twantName:     \"service_error_rate\",\n\t\t\t\twantDesc:     \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\t\t\twantLabels: []map[string]string{\n\t\t\t\t\t{\"service_name\": \"driver\"},\n\t\t\t\t},\n\t\t\t\twantPoints: nil,\n\t\t\t},\n\t\t\tcallRateFile: mockEmptyResponse,\n\t\t},\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"error query fails\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    false,\n\t\t\t\tresponseFile: mockErrorResponse,\n\t\t\t\twantErr:      \"failed executing metrics query\",\n\t\t\t},\n\t\t\tcallRateFile: mockCallRateResponse,\n\t\t},\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"call rate query fails\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    false,\n\t\t\t\tresponseFile: mockErrorRateResponse,\n\t\t\t\twantErr:      \"failed executing metrics query\",\n\t\t\t},\n\t\t\tcallRateFile: mockErrorResponse,\n\t\t},\n\t\t{\n\t\t\tmetricsTestCase: metricsTestCase{\n\t\t\t\tname:         \"convert error\",\n\t\t\t\tserviceNames: []string{\"driver\"},\n\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\tgroupByOp:    true,\n\t\t\t\tresponseFile: \"testdata/output_error_latencies.json\",\n\t\t\t\twantErr:      \"failed to convert aggregations to metrics\",\n\t\t\t},\n\t\t\tcallRateFile: mockCallRateResponse,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmockServer := startMockEsErrorRateServer(t, tc.query, tc.responseFile, tc.callRateFile)\n\t\t\tdefer mockServer.Close()\n\t\t\treader, exporter := setupMetricsReaderFromServer(t, mockServer)\n\t\t\tparams := &metricstore.ErrorRateQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParameters(tc.metricsTestCase),\n\t\t\t}\n\n\t\t\tmetricFamily, err := reader.GetErrorRates(context.Background(), params)\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, tc.wantErr)\n\t\t\t\tassert.Nil(t, metricFamily)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassertMetricFamily(t, metricFamily, metricsTestCase{\n\t\t\t\t\twantName:   tc.wantName,\n\t\t\t\t\twantDesc:   tc.wantDesc,\n\t\t\t\t\twantLabels: tc.wantLabels,\n\t\t\t\t\twantPoints: tc.wantPoints,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tspans := exporter.GetSpans()\n\t\t\tif tc.wantErr == \"\" {\n\t\t\t\tassert.GreaterOrEqual(t, len(spans), 1, \"Expected at least one span for the Elasticsearch queries\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetMinStepDuration(t *testing.T) {\n\tmockServer := startMockEsServer(t, \"\", mockEsValidResponse)\n\tdefer mockServer.Close()\n\treader, _ := setupMetricsReaderFromServer(t, mockServer)\n\tminStep, err := reader.GetMinStepDuration(context.Background(), &metricstore.MinStepDurationQueryParameters{})\n\trequire.NoError(t, err)\n\tassert.Equal(t, time.Millisecond, minStep)\n}\n\nfunc TestGetCallRateBucketsToPoints_ErrorCases(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tbuckets []*elastic.AggregationBucketHistogramItem\n\t}{\n\t\t{\n\t\t\tname: \"nil cumulative sum value\",\n\t\t\tbuckets: []*elastic.AggregationBucketHistogramItem{\n\t\t\t\t{\n\t\t\t\t\tKey:      1749894900000,\n\t\t\t\t\tDocCount: 1,\n\t\t\t\t\tAggregations: map[string]json.RawMessage{\n\t\t\t\t\t\tculmuAggName: json.RawMessage(`{\"value\": null}`),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := bucketsToCallRate(tt.buckets)\n\t\t\tassert.True(t, math.IsNaN(result[0].Value))\n\t\t})\n\t}\n}\n\nfunc isErrorQuery(query map[string]any) bool {\n\tif q, ok := query[\"query\"].(map[string]any); ok {\n\t\tif b, ok := q[\"bool\"].(map[string]any); ok {\n\t\t\tif filters, ok := b[\"filter\"].([]any); ok {\n\t\t\t\tfor _, f := range filters {\n\t\t\t\t\tif term, ok := f.(map[string]any); ok {\n\t\t\t\t\t\tif _, ok := term[\"term\"].(map[string]any); ok {\n\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc sendResponse(t *testing.T, w http.ResponseWriter, responseFile string) {\n\tbytes, err := os.ReadFile(responseFile)\n\trequire.NoError(t, err)\n\n\t_, err = w.Write(bytes)\n\trequire.NoError(t, err)\n}\n\nfunc startMockEsErrorRateServer(t *testing.T, wantEsQuery string, responseFile string, callRateResponseFile string) *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusOK)\n\t\t// Handle initial ping request\n\t\tif r.Method == http.MethodHead || r.URL.Path == \"/\" {\n\t\t\tsendResponse(t, w, mockEsValidResponse)\n\t\t\treturn\n\t\t}\n\n\t\t// Read request body\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tassert.NoError(t, err, \"Failed to read request body\")\n\t\tdefer r.Body.Close()\n\n\t\t// Determine which response to return based on query content\n\t\tvar query map[string]any\n\t\tjson.Unmarshal(body, &query)\n\n\t\t// Check if this is an error query (contains error term filter)\n\t\tif isErrorQuery(query) {\n\t\t\t// Validate query if provided\n\t\t\tcheckQuery(t, wantEsQuery, body)\n\t\t\tsendResponse(t, w, responseFile)\n\t\t} else {\n\t\t\tsendResponse(t, w, callRateResponseFile)\n\t\t}\n\t}))\n}\n\nfunc startMockEsServer(t *testing.T, wantEsQuery string, responseFile string) *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusOK)\n\n\t\t// Handle initial ping request\n\t\tif r.Method == http.MethodHead || r.URL.Path == \"/\" {\n\t\t\tsendResponse(t, w, mockEsValidResponse)\n\t\t\treturn\n\t\t}\n\n\t\t// Read request body\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tassert.NoError(t, err, \"Failed to read request body\")\n\t\tdefer r.Body.Close()\n\n\t\t// Validate query if provided\n\t\tcheckQuery(t, wantEsQuery, body)\n\t\tsendResponse(t, w, responseFile)\n\t}))\n}\n\nfunc checkQuery(t *testing.T, wantEsQuery string, body []byte) {\n\tif wantEsQuery != \"\" {\n\t\tvar expected, actual map[string]any\n\t\tassert.NoError(t, json.Unmarshal([]byte(wantEsQuery), &expected))\n\t\tassert.NoError(t, json.Unmarshal(body, &actual))\n\t\tnormalizeScripts(expected)\n\t\tnormalizeScripts(actual)\n\n\t\tcompareQueryStructure(t, expected, actual)\n\t}\n}\n\nfunc normalizeScripts(m any) {\n\tif m, ok := m.(map[string]any); ok {\n\t\tif script, ok := m[\"script\"].(map[string]any); ok {\n\t\t\tif source, ok := script[\"source\"].(string); ok {\n\t\t\t\t// Remove whitespace and newlines for comparison\n\t\t\t\tscript[\"source\"] = strings.Join(strings.Fields(source), \" \")\n\t\t\t}\n\t\t}\n\t\tfor _, v := range m {\n\t\t\tnormalizeScripts(v)\n\t\t}\n\t}\n}\n\nfunc compareQueryStructure(t *testing.T, expected, actual map[string]any) {\n\t// Compare the bool query structure (without time ranges)\n\tif expectedQuery, ok := expected[\"query\"].(map[string]any); ok {\n\t\tactualQuery := actual[\"query\"].(map[string]any)\n\t\tcompareBoolQuery(t, expectedQuery, actualQuery)\n\t}\n\n\t// Compare aggregations\n\tif expectedAggs, ok := expected[\"aggregations\"].(map[string]any); ok {\n\t\tactualAggs := actual[\"aggregations\"].(map[string]any)\n\t\t// For convenience, we remove date_histogram for easier comparison here because date_histogram includes time bounds which can vary by a few milliseconds\n\t\tremoveHistogramBounds(expectedAggs)\n\t\tremoveHistogramBounds(actualAggs)\n\n\t\tassert.Equal(t, expectedAggs, actualAggs, \"Aggregations mismatch\")\n\t}\n}\n\n// Simple helper to remove extended_bounds from any date_histogram\nfunc removeHistogramBounds(aggs map[string]any) {\n\tfor _, agg := range aggs {\n\t\taggMap, ok := agg.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Remove from date_histogram if present\n\t\tif histo, ok := aggMap[\"date_histogram\"].(map[string]any); ok {\n\t\t\tdelete(histo, \"extended_bounds\")\n\t\t}\n\n\t\t// Handle nested aggregations\n\t\tif nested, ok := aggMap[\"aggregations\"].(map[string]any); ok {\n\t\t\tremoveHistogramBounds(nested)\n\t\t}\n\t}\n}\n\nfunc compareBoolQuery(t *testing.T, expected, actual map[string]any) {\n\texpectedBool, eok := expected[\"bool\"].(map[string]any)\n\tactualBool, aok := actual[\"bool\"].(map[string]any)\n\n\tif !eok || !aok {\n\t\treturn\n\t}\n\n\t// Compare filters (excluding time ranges)\n\tif expectedFilters, ok := expectedBool[\"filter\"].([]any); ok {\n\t\tactualFilters := actualBool[\"filter\"].([]any)\n\t\tcompareFilters(t, expectedFilters, actualFilters)\n\t}\n}\n\nfunc compareFilters(t *testing.T, expected, actual []any) {\n\t// We'll compare the same number of filters, but skip time ranges\n\tassert.Len(t, actual, len(expected), \"Different number of filters\")\n\n\tfor i := range expected {\n\t\texpectedFilter := expected[i].(map[string]any)\n\t\tactualFilter := actual[i].(map[string]any)\n\n\t\t// Skip range queries entirely\n\t\tif _, isRange := expectedFilter[\"range\"]; isRange {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equal(t, expectedFilter, actualFilter, \"Filter mismatch at index %d\", i)\n\t}\n}\n\nfunc setupMetricsReaderFromServer(t *testing.T, mockServer *httptest.Server) (*MetricsReader, *tracetest.InMemoryExporter) {\n\tlogger, _ := zap.NewDevelopment() // Use development logger for client-side logs\n\ttracer, exporter := tracerProvider(t)\n\n\tcfg := config.Configuration{\n\t\tServers:  []string{mockServer.URL},\n\t\tLogLevel: \"debug\",\n\t\tTags: config.TagsAsFields{\n\t\t\tInclude:        \"span.kind,error\",\n\t\t\tDotReplacement: \"@\",\n\t\t},\n\t}\n\n\tclient := clientProvider(t, &cfg, logger, esmetrics.NullFactory)\n\treader := NewMetricsReader(client, cfg, logger, tracer)\n\trequire.NotNil(t, reader)\n\n\treturn reader, exporter\n}\n\nfunc buildTestBaseQueryParameters(tc metricsTestCase) metricstore.BaseQueryParameters {\n\tendTime := time.UnixMilli(1749894900000)\n\tlookback := 6 * time.Hour\n\tstep := time.Minute\n\tratePer := 10 * time.Minute\n\n\treturn metricstore.BaseQueryParameters{\n\t\tServiceNames:     tc.serviceNames,\n\t\tGroupByOperation: tc.groupByOp,\n\t\tEndTime:          &endTime,\n\t\tLookback:         &lookback,\n\t\tStep:             &step,\n\t\tRatePer:          &ratePer,\n\t\tSpanKinds:        tc.spanKinds,\n\t}\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_call_rate.json",
    "content": "{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 100,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": [\n        {\n          \"key_as_string\": \"1749894840000\",\n          \"key\": 1749894840000,\n          \"doc_count\": 0,\n          \"cumulative_requests\": {\n            \"value\": 0\n          }\n        },\n        {\n          \"key_as_string\": \"1749894900000\",\n          \"key\": 1749894900000,\n          \"doc_count\": 10,\n          \"cumulative_requests\": {\n            \"value\": 10\n          }\n        },\n        {\n          \"key_as_string\": \"1749894960000\",\n          \"key\": 1749894960000,\n          \"doc_count\": 20,\n          \"cumulative_requests\": {\n            \"value\": 30\n          }\n        },\n        {\n          \"key_as_string\": \"1749895020000\",\n          \"key\": 1749895020000,\n          \"doc_count\": 30,\n          \"cumulative_requests\": {\n            \"value\": 60\n          }\n        },\n        {\n          \"key_as_string\": \"1749895080000\",\n          \"key\": 1749895080000,\n          \"doc_count\": 40,\n          \"cumulative_requests\": {\n            \"value\": 100\n          }\n        },\n        {\n          \"key_as_string\": \"1749895140000\",\n          \"key\": 1749895140000,\n          \"doc_count\": 50,\n          \"cumulative_requests\": {\n            \"value\": 150\n          }\n        },\n        {\n          \"key_as_string\": \"1749895200000\",\n          \"key\": 1749895200000,\n          \"doc_count\": 60,\n          \"cumulative_requests\": {\n            \"value\": 210\n          }\n        },\n        {\n          \"key_as_string\": \"1749895260000\",\n          \"key\": 1749895260000,\n          \"doc_count\": 70,\n          \"cumulative_requests\": {\n            \"value\": 280\n          }\n        },\n        {\n          \"key_as_string\": \"1749895320000\",\n          \"key\": 1749895320000,\n          \"doc_count\": 80,\n          \"cumulative_requests\": {\n            \"value\": 360\n          }\n        },\n        {\n          \"key_as_string\": \"1749895380000\",\n          \"key\": 1749895380000,\n          \"doc_count\": 90,\n          \"cumulative_requests\": {\n            \"value\": 450\n          }\n        },\n        {\n          \"key_as_string\": \"1749895440000\",\n          \"key\": 1749895440000,\n          \"doc_count\": 100,\n          \"cumulative_requests\": {\n            \"value\": 550\n          }\n        },\n        {\n          \"key_as_string\": \"1749895500000\",\n          \"key\": 1749895500000,\n          \"doc_count\": 0,\n          \"cumulative_requests\": {\n            \"value\": 0\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_call_rate_operation.json",
    "content": "{\n  \"took\": 501,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 5,\n    \"successful\": 5,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 10000,\n    \"max_score\": null,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"doc_count_error_upper_bound\": 0,\n      \"sum_other_doc_count\": 0,\n      \"buckets\": [\n        {\n          \"key\": \"/FindCar\",\n          \"doc_count\": 10,\n          \"date_histogram\": {\n            \"buckets\": [\n              {\"key\": 1749894840000, \"doc_count\": 10, \"cumulative_requests\": {\"value\": 10}}\n            ]\n          }\n        },\n        {\n          \"key\": \"/FindDriverIDs\",\n          \"doc_count\": 200,\n          \"date_histogram\": {\n            \"buckets\": [\n              {\"key\": 1749894840000, \"doc_count\": 0, \"cumulative_requests\": {\"value\": 0}},\n              {\"key\": 1749894900000, \"doc_count\": 10, \"cumulative_requests\": {\"value\": 10}},\n              {\"key\": 1749894960000, \"doc_count\": 20, \"cumulative_requests\": {\"value\": 30}},\n              {\"key\": 1749895020000, \"doc_count\": 30, \"cumulative_requests\": {\"value\": 60}},\n              {\"key\": 1749895080000, \"doc_count\": 40, \"cumulative_requests\": {\"value\": 100}},\n              {\"key\": 1749895140000, \"doc_count\": 50, \"cumulative_requests\": {\"value\": 150}},\n              {\"key\": 1749895200000, \"doc_count\": 60, \"cumulative_requests\": {\"value\": 210}},\n              {\"key\": 1749895260000, \"doc_count\": 70, \"cumulative_requests\": {\"value\": 280}},\n              {\"key\": 1749895320000, \"doc_count\": 80, \"cumulative_requests\": {\"value\": 360}},\n              {\"key\": 1749895380000, \"doc_count\": 90, \"cumulative_requests\": {\"value\": 450}},\n              {\"key\": 1749895440000, \"doc_count\": 100, \"cumulative_requests\": {\"value\": 550}}\n            ]\n          }\n        },\n        {\n          \"key\": \"/FindNearest\",\n          \"doc_count\": 100,\n          \"date_histogram\": {\n            \"buckets\": [\n              {\n                \"key_as_string\": \"1749894840000\",\n                \"key\": 1749894840000,\n                \"doc_count\": 0,\n                \"cumulative_requests\": {\n                  \"value\": 0\n                }\n              },\n              {\n                \"key_as_string\": \"1749894900000\",\n                \"key\": 1749894900000,\n                \"doc_count\": 10,\n                \"cumulative_requests\": {\n                  \"value\": 10\n                }\n              },\n              {\n                \"key_as_string\": \"1749894960000\",\n                \"key\": 1749894960000,\n                \"doc_count\": 20,\n                \"cumulative_requests\": {\n                  \"value\": 30\n                }\n              },\n              {\n                \"key_as_string\": \"1749895020000\",\n                \"key\": 1749895020000,\n                \"doc_count\": 30,\n                \"cumulative_requests\": {\n                  \"value\": 60\n                }\n              },\n              {\n                \"key_as_string\": \"1749895080000\",\n                \"key\": 1749895080000,\n                \"doc_count\": 40,\n                \"cumulative_requests\": {\n                  \"value\": 100\n                }\n              },\n              {\n                \"key_as_string\": \"1749895140000\",\n                \"key\": 1749895140000,\n                \"doc_count\": 50,\n                \"cumulative_requests\": {\n                  \"value\": 150\n                }\n              },\n              {\n                \"key_as_string\": \"1749895200000\",\n                \"key\": 1749895200000,\n                \"doc_count\": 60,\n                \"cumulative_requests\": {\n                  \"value\": 210\n                }\n              },\n              {\n                \"key_as_string\": \"1749895260000\",\n                \"key\": 1749895260000,\n                \"doc_count\": 70,\n                \"cumulative_requests\": {\n                  \"value\": 280\n                }\n              },\n              {\n                \"key_as_string\": \"1749895320000\",\n                \"key\": 1749895320000,\n                \"doc_count\": 80,\n                \"cumulative_requests\": {\n                  \"value\": 360\n                }\n              },\n              {\n                \"key_as_string\": \"1749895380000\",\n                \"key\": 1749895380000,\n                \"doc_count\": 90,\n                \"cumulative_requests\": {\n                  \"value\": 450\n                }\n              },\n              {\n                \"key_as_string\": \"1749895440000\",\n                \"key\": 1749895440000,\n                \"doc_count\": 100,\n                \"cumulative_requests\": {\n                  \"value\": 550\n                }\n              },\n              {\n                \"key_as_string\": \"1749895500000\",\n                \"key\": 1749895500000,\n                \"doc_count\": 0,\n                \"cumulative_requests\": {\n                  \"value\": 0\n                }\n              }\n            ]\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_empty.json",
    "content": "\n{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 0,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": []\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_error_es.json",
    "content": "{\"error\": \"internal server error\"}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_error_latencies.json",
    "content": "\n{\n\t\"took\": 5,\n\t\"timed_out\": false,\n\t\"_shards\": {\n\t\t\"total\": 1,\n\t\t\"successful\": 1,\n\t\t\"skipped\": 0,\n\t\t\"failed\": 0\n\t},\n\t\"hits\": {\n\t\t\"total\": 100,\n\t\t\"max_score\": 0.0,\n\t\t\"hits\": []\n\t},\n\t\"aggregations\": {\n\t\t\"results_buckets\": {\n\t\t\t\"doc_count_error_upper_bound\": 0,\n\t\t\t\"sum_other_doc_count\": 0,\n\t\t\t\"buckets\": [\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"/FindNearest\",\n\t\t\t\t\t\"doc_count\": 100,\n\t\t\t\t\t\"error_not_find_datehistogram\": {\n\t\t\t\t\t\t\"buckets\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key_as_string\": \"1749894900000\",\n\t\t\t\t\t\t\t\t\"key\": 1749894900000,\n\t\t\t\t\t\t\t\t\"doc_count\": 50,\n\t\t\t\t\t\t\t\t\"percentiles_of_bucket\": {\n\t\t\t\t\t\t\t\t\t\"values\": {\n\t\t\t\t\t\t\t\t\t\t\"95.0\": 200.0\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_errors_rate.json",
    "content": "{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 50,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": [\n        {\n          \"key_as_string\": \"1749894840000\",\n          \"key\": 1749894840000,\n          \"doc_count\": 0,\n          \"cumulative_requests\": {\n            \"value\": 0\n          }\n        },\n        {\n          \"key_as_string\": \"1749894900000\",\n          \"key\": 1749894900000,\n          \"doc_count\": 5,\n          \"cumulative_requests\": {\n            \"value\": 5\n          }\n        },\n        {\n          \"key_as_string\": \"1749894960000\",\n          \"key\": 1749894960000,\n          \"doc_count\": 10,\n          \"cumulative_requests\": {\n            \"value\": 15\n          }\n        },\n        {\n          \"key_as_string\": \"1749895020000\",\n          \"key\": 1749895020000,\n          \"doc_count\": 15,\n          \"cumulative_requests\": {\n            \"value\": 30\n          }\n        },\n        {\n          \"key_as_string\": \"1749895080000\",\n          \"key\": 1749895080000,\n          \"doc_count\": 20,\n          \"cumulative_requests\": {\n            \"value\": 50\n          }\n        },\n        {\n          \"key_as_string\": \"1749895140000\",\n          \"key\": 1749895140000,\n          \"doc_count\": 25,\n          \"cumulative_requests\": {\n            \"value\": 75\n          }\n        },\n        {\n          \"key_as_string\": \"1749895200000\",\n          \"key\": 1749895200000,\n          \"doc_count\": 30,\n          \"cumulative_requests\": {\n            \"value\": 105\n          }\n        },\n        {\n          \"key_as_string\": \"1749895260000\",\n          \"key\": 1749895260000,\n          \"doc_count\": 35,\n          \"cumulative_requests\": {\n            \"value\": 140\n          }\n        },\n        {\n          \"key_as_string\": \"1749895320000\",\n          \"key\": 1749895320000,\n          \"doc_count\": 40,\n          \"cumulative_requests\": {\n            \"value\": 180\n          }\n        },\n        {\n          \"key_as_string\": \"1749895380000\",\n          \"key\": 1749895380000,\n          \"doc_count\": 45,\n          \"cumulative_requests\": {\n            \"value\": 225\n          }\n        },\n        {\n          \"key_as_string\": \"1749895440000\",\n          \"key\": 1749895440000,\n          \"doc_count\": 50,\n          \"cumulative_requests\": {\n            \"value\": 415\n          }\n        },\n        {\n          \"key_as_string\": \"1749895500000\",\n          \"key\": 1749895500000,\n          \"doc_count\": 0,\n          \"cumulative_requests\": {\n            \"value\": 415\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_errors_rate_operation.json",
    "content": "{\n  \"took\": 501,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 5,\n    \"successful\": 5,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 10000,\n    \"max_score\": null,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"doc_count_error_upper_bound\": 0,\n      \"sum_other_doc_count\": 0,\n      \"buckets\": [\n        {\n          \"key\": \"/FindNearest\",\n          \"doc_count\": 100,\n          \"date_histogram\": {\n            \"buckets\": [\n              {\n                \"key_as_string\": \"1749894840000\",\n                \"key\": 1749894840000,\n                \"doc_count\": 0,\n                \"cumulative_requests\": {\n                  \"value\": 0\n                }\n              },\n              {\n                \"key_as_string\": \"1749894900000\",\n                \"key\": 1749894900000,\n                \"doc_count\": 5,\n                \"cumulative_requests\": {\n                  \"value\": 5\n                }\n              },\n              {\n                \"key_as_string\": \"1749894960000\",\n                \"key\": 1749894960000,\n                \"doc_count\": 10,\n                \"cumulative_requests\": {\n                  \"value\": 15\n                }\n              },\n              {\n                \"key_as_string\": \"1749895020000\",\n                \"key\": 1749895020000,\n                \"doc_count\": 15,\n                \"cumulative_requests\": {\n                  \"value\": 30\n                }\n              },\n              {\n                \"key_as_string\": \"1749895080000\",\n                \"key\": 1749895080000,\n                \"doc_count\": 20,\n                \"cumulative_requests\": {\n                  \"value\": 50\n                }\n              },\n              {\n                \"key_as_string\": \"1749895140000\",\n                \"key\": 1749895140000,\n                \"doc_count\": 25,\n                \"cumulative_requests\": {\n                  \"value\": 75\n                }\n              },\n              {\n                \"key_as_string\": \"1749895200000\",\n                \"key\": 1749895200000,\n                \"doc_count\": 30,\n                \"cumulative_requests\": {\n                  \"value\": 105\n                }\n              },\n              {\n                \"key_as_string\": \"1749895260000\",\n                \"key\": 1749895260000,\n                \"doc_count\": 35,\n                \"cumulative_requests\": {\n                  \"value\": 140\n                }\n              },\n              {\n                \"key_as_string\": \"1749895320000\",\n                \"key\": 1749895320000,\n                \"doc_count\": 40,\n                \"cumulative_requests\": {\n                  \"value\": 180\n                }\n              },\n              {\n                \"key_as_string\": \"1749895380000\",\n                \"key\": 1749895380000,\n                \"doc_count\": 45,\n                \"cumulative_requests\": {\n                  \"value\": 225\n                }\n              },\n              {\n                \"key_as_string\": \"1749895440000\",\n                \"key\": 1749895440000,\n                \"doc_count\": 50,\n                \"cumulative_requests\": {\n                  \"value\": 415\n                }\n              },\n              {\n                \"key_as_string\": \"1749895500000\",\n                \"key\": 1749895500000,\n                \"doc_count\": 0,\n                \"cumulative_requests\": {\n                  \"value\": 415\n                }\n              }\n            ]\n          }\n        },\n        {\n          \"key\": \"/FindDriverIDs\",\n          \"doc_count\": 200,\n          \"date_histogram\": {\n            \"buckets\": [\n              {\"key\": 1749894840000, \"doc_count\": 0, \"cumulative_requests\": {\"value\": 0}},\n              {\"key\": 1749894900000, \"doc_count\": 8, \"cumulative_requests\": {\"value\": 8}},\n              {\"key\": 1749894960000, \"doc_count\": 16, \"cumulative_requests\": {\"value\": 24}},\n              {\"key\": 1749895020000, \"doc_count\": 24, \"cumulative_requests\": {\"value\": 48}},\n              {\"key\": 1749895080000, \"doc_count\": 32, \"cumulative_requests\": {\"value\": 80}},\n              {\"key\": 1749895140000, \"doc_count\": 40, \"cumulative_requests\": {\"value\": 120}},\n              {\"key\": 1749895200000, \"doc_count\": 48, \"cumulative_requests\": {\"value\": 168}},\n              {\"key\": 1749895260000, \"doc_count\": 56, \"cumulative_requests\": {\"value\": 224}},\n              {\"key\": 1749895320000, \"doc_count\": 64, \"cumulative_requests\": {\"value\": 288}},\n              {\"key\": 1749895380000, \"doc_count\": 72, \"cumulative_requests\": {\"value\": 360}},\n              {\"key\": 1749895440000, \"doc_count\": 80, \"cumulative_requests\": {\"value\": 440}}\n            ]\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_latencies.json",
    "content": "\n{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 100,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": [\n        {\n          \"key_as_string\": \"1749894900000\",\n          \"key\": 1749894900000,\n          \"doc_count\": 50,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 200.0\n            }\n          }\n        },\n        {\n          \"key_as_string\": \"1749894960000\",\n          \"key\": 1749894960000,\n          \"doc_count\": 60,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 210.0\n            }\n          }\n        },\n        {\n          \"key_as_string\": \"1749895020000\",\n          \"key\": 1749895020000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 0\n            }\n          }\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_latencies_50.json",
    "content": "{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 100,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": [\n        {\n          \"key\": 1749894840000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 0.0\n            }\n          }\n        },\n        {\n          \"key\": 1749894900000,\n          \"doc_count\": 50,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 150.0\n            }\n          }\n        },\n        {\n          \"key\": 1749894960000,\n          \"doc_count\": 60,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 160.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895020000,\n          \"doc_count\": 70,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 170.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895080000,\n          \"doc_count\": 80,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 180.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895140000,\n          \"doc_count\": 90,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 190.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895200000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 0.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895260000,\n          \"doc_count\": 100,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 200.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895320000,\n          \"doc_count\": 110,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 210.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895380000,\n          \"doc_count\": 120,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 220.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895440000,\n          \"doc_count\": 130,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"50.0\": 230.0\n            }\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_latencies_75.json",
    "content": "{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 100,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": [\n        {\n          \"key\": 1749894840000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 0.0\n            }\n          }\n        },\n        {\n          \"key\": 1749894900000,\n          \"doc_count\": 50,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 250.0\n            }\n          }\n        },\n        {\n          \"key\": 1749894960000,\n          \"doc_count\": 60,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 260.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895020000,\n          \"doc_count\": 70,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 270.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895080000,\n          \"doc_count\": 80,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 280.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895140000,\n          \"doc_count\": 90,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 290.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895200000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 0.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895260000,\n          \"doc_count\": 100,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 300.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895320000,\n          \"doc_count\": 110,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 310.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895380000,\n          \"doc_count\": 120,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 320.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895440000,\n          \"doc_count\": 130,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"75.0\": 330.0\n            }\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_latencies_95.json",
    "content": "{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 100,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"buckets\": [\n        {\n          \"key\": 1749894840000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 0.0\n            }\n          }\n        },\n        {\n          \"key\": 1749894900000,\n          \"doc_count\": 50,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 450.0\n            }\n          }\n        },\n        {\n          \"key\": 1749894960000,\n          \"doc_count\": 60,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 460.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895020000,\n          \"doc_count\": 70,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 470.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895080000,\n          \"doc_count\": 80,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 480.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895140000,\n          \"doc_count\": 90,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 490.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895200000,\n          \"doc_count\": 0,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 0.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895260000,\n          \"doc_count\": 100,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 500.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895320000,\n          \"doc_count\": 110,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 510.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895380000,\n          \"doc_count\": 120,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 520.0\n            }\n          }\n        },\n        {\n          \"key\": 1749895440000,\n          \"doc_count\": 130,\n          \"percentiles_of_bucket\": {\n            \"values\": {\n              \"95.0\": 530.0\n            }\n          }\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_latencies_operation.json",
    "content": "\n{\n  \"took\": 5,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": 100,\n    \"max_score\": 0.0,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"results_buckets\": {\n      \"doc_count_error_upper_bound\": 0,\n      \"sum_other_doc_count\": 0,\n      \"buckets\": [\n        {\n          \"key\": \"/FindNearest\",\n          \"doc_count\": 100,\n          \"date_histogram\": {\n            \"buckets\": [\n              {\n                \"key_as_string\": \"1749894900000\",\n                \"key\": 1749894900000,\n                \"doc_count\": 50,\n                \"percentiles_of_bucket\": {\n                  \"values\": {\n                    \"95.0\": 200.0\n                  }\n                }\n              },\n              {\n                \"key_as_string\": \"1749894960000\",\n                \"key\": 1749894960000,\n                \"doc_count\": 60,\n                \"percentiles_of_bucket\": {\n                  \"values\": {\n                    \"95.0\": 210.0\n                  }\n                }\n              }\n            ]\n          }\n        }\n        ]\n      }\n    }\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/testdata/output_valid_es.json",
    "content": "{\n  \"version\": {\n    \"number\": \"6.8.0\"\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/to_domain.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/types\"\n\t\"github.com/olivere/elastic/v7\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n)\n\n// Translator converts raw Elasticsearch aggregation results into Jaeger's metrics domain model\n// (metrics.MetricFamily). It uses a configurable function to extract values from buckets,\n// ensuring flexibility across different metric types (e.g., latencies, call rates).\ntype Translator struct {\n\tbucketsToPointsFunc func(buckets []*elastic.AggregationBucketHistogramItem) []*Pair\n}\n\nfunc NewTranslator(bucketsToPointsFunc func(buckets []*elastic.AggregationBucketHistogramItem) []*Pair) Translator {\n\treturn Translator{\n\t\tbucketsToPointsFunc: bucketsToPointsFunc,\n\t}\n}\n\n// ToDomainMetricsFamily converts Elasticsearch aggregations to Jaeger's MetricFamily.\nfunc (t *Translator) ToDomainMetricsFamily(m MetricsQueryParams, result *elastic.SearchResult) (*metrics.MetricFamily, error) {\n\tdomainMetrics, err := t.toDomainMetrics(m, result)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert aggregations to metrics: %w\", err)\n\t}\n\n\tif m.GroupByOperation {\n\t\tm.metricName = strings.Replace(m.metricName, \"service\", \"service_operation\", 1)\n\t\tm.metricDesc += \" & operation\"\n\t}\n\n\treturn &metrics.MetricFamily{\n\t\tName:    m.metricName,\n\t\tType:    metrics.MetricType_GAUGE,\n\t\tHelp:    m.metricDesc,\n\t\tMetrics: domainMetrics,\n\t}, nil\n}\n\n// toDomainMetrics converts Elasticsearch aggregations to Jaeger metrics.\nfunc (t *Translator) toDomainMetrics(m MetricsQueryParams, result *elastic.SearchResult) ([]*metrics.Metric, error) {\n\tlabels := buildServiceLabels(m.ServiceNames)\n\n\tif !m.GroupByOperation {\n\t\tbuckets, err := extractBuckets(result)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*metrics.Metric{\n\t\t\t{\n\t\t\t\tLabels:       labels,\n\t\t\t\tMetricPoints: toDomainMetricPoints(t.bucketsToPointsFunc(buckets)),\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// Handle grouped results when groupByOp is true\n\tagg, found := result.Aggregations.Terms(aggName)\n\tif !found {\n\t\treturn nil, fmt.Errorf(\"%s aggregation not found\", aggName)\n\t}\n\n\tvar metricsData []*metrics.Metric\n\tfor _, bucket := range agg.Buckets {\n\t\tmetric, err := t.processOperationBucket(bucket, labels)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to process bucket: %w\", err)\n\t\t}\n\t\tmetricsData = append(metricsData, metric)\n\t}\n\n\treturn metricsData, nil\n}\n\nfunc buildServiceLabels(serviceNames []string) []*metrics.Label {\n\tlabels := make([]*metrics.Label, len(serviceNames))\n\tfor i, name := range serviceNames {\n\t\tlabels[i] = &metrics.Label{Name: \"service_name\", Value: name}\n\t}\n\treturn labels\n}\n\nfunc (t *Translator) processOperationBucket(bucket *elastic.AggregationBucketKeyItem, baseLabels []*metrics.Label) (*metrics.Metric, error) {\n\tkey, ok := bucket.Key.(string)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"bucket key is not a string: %v\", bucket.Key)\n\t}\n\n\t// Extract nested date_histogram buckets\n\tdateHistAgg, found := bucket.Aggregations.DateHistogram(dateHistAggName)\n\tif !found {\n\t\treturn nil, fmt.Errorf(\"date_histogram aggregation not found in bucket %q\", key)\n\t}\n\n\t// Combine base labels with operation label\n\tlabels := append(baseLabels, toDomainLabels(key)...)\n\n\treturn &metrics.Metric{\n\t\tLabels:       labels,\n\t\tMetricPoints: toDomainMetricPoints(t.bucketsToPointsFunc(dateHistAgg.Buckets)),\n\t}, nil\n}\n\n// toDomainLabels converts the bucket key to Jaeger metric labels.\nfunc toDomainLabels(key string) []*metrics.Label {\n\treturn []*metrics.Label{\n\t\t{\n\t\t\tName:  \"operation\",\n\t\t\tValue: key,\n\t\t},\n\t}\n}\n\n// extractBuckets retrieves date histogram buckets from Elasticsearch results.\nfunc extractBuckets(result *elastic.SearchResult) ([]*elastic.AggregationBucketHistogramItem, error) {\n\tagg, found := result.Aggregations.DateHistogram(aggName)\n\tif !found {\n\t\treturn nil, fmt.Errorf(\"%s aggregation not found\", aggName)\n\t}\n\treturn agg.Buckets, nil\n}\n\n// toDomainMetricPoints converts Elasticsearch buckets to Jaeger metric points.\nfunc toDomainMetricPoints(rawResult []*Pair) []*metrics.MetricPoint {\n\tmetricPoints := make([]*metrics.MetricPoint, 0, len(rawResult))\n\tfor _, pair := range rawResult {\n\t\tmp := toDomainMetricPoint(pair)\n\t\tif mp != nil {\n\t\t\tmetricPoints = append(metricPoints, mp)\n\t\t}\n\t}\n\n\treturn metricPoints\n}\n\n// toDomainMetricPoint converts a single Pair to a Jaeger metric point.\nfunc toDomainMetricPoint(pair *Pair) *metrics.MetricPoint {\n\ttimestamp := toDomainTimestamp(pair.TimeStamp)\n\tif timestamp == nil {\n\t\treturn nil\n\t}\n\n\treturn &metrics.MetricPoint{\n\t\tValue:     toDomainMetricPointValue(pair.Value),\n\t\tTimestamp: timestamp,\n\t}\n}\n\n// toDomainTimestamp converts milliseconds since epoch to protobuf Timestamp.\nfunc toDomainTimestamp(millis int64) *types.Timestamp {\n\ttimestamp := time.Unix(0, millis*int64(time.Millisecond))\n\tprotoTimestamp, _ := types.TimestampProto(timestamp)\n\treturn protoTimestamp\n}\n\n// toDomainMetricPointValue converts a float64 value to Jaeger's gauge metric point.\nfunc toDomainMetricPointValue(value float64) *metrics.MetricPoint_GaugeValue {\n\treturn &metrics.MetricPoint_GaugeValue{\n\t\tGaugeValue: &metrics.GaugeValue{\n\t\t\tValue: &metrics.GaugeValue_DoubleValue{DoubleValue: value},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/storage/metricstore/elasticsearch/to_domain_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/types\"\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\nfunc TestCreateNewTranslator(t *testing.T) {\n\ttranslator := NewTranslator(bucketsToCallRate)\n\trequire.NotNil(t, translator)\n}\n\nfunc TestToMetricsFamily(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tparams   MetricsQueryParams\n\t\tresult   *elastic.SearchResult\n\t\texpected *metrics.MetricFamily\n\t\terr      string\n\t}{\n\t\t{\n\t\t\tname:   \"successful conversion\",\n\t\t\tparams: mockMetricsQueryParams([]string{\"service1\"}, false),\n\t\t\tresult: createTestSearchResult(false),\n\t\t\texpected: &metrics.MetricFamily{\n\t\t\t\tName: \"test_metric\",\n\t\t\t\tType: metrics.MetricType_GAUGE,\n\t\t\t\tHelp: \"test description\",\n\t\t\t\tMetrics: []*metrics.Metric{\n\t\t\t\t\t{\n\t\t\t\t\t\tLabels: []*metrics.Label{\n\t\t\t\t\t\t\t{Name: \"service_name\", Value: \"service1\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tMetricPoints: []*metrics.MetricPoint{\n\t\t\t\t\t\t\tcreateEpochGaugePoint(1.23),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"missing aggregation\",\n\t\t\tparams: MetricsQueryParams{\n\t\t\t\tmetricName: \"test_metric\",\n\t\t\t},\n\t\t\tresult: &elastic.SearchResult{\n\t\t\t\tAggregations: make(elastic.Aggregations),\n\t\t\t},\n\t\t\terr: \"results_buckets aggregation not found\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttranslator := mockTranslator()\n\t\t\tgot, err := translator.ToDomainMetricsFamily(tt.params, tt.result)\n\t\t\tif tt.err != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, tt.err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tt.expected, got)\n\t\t})\n\t}\n}\n\nfunc TestToDomainMetrics(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tparams   MetricsQueryParams\n\t\tresult   *elastic.SearchResult\n\t\texpected []*metrics.Metric\n\t\terr      string\n\t}{\n\t\t{\n\t\t\tname:   \"simple metrics\",\n\t\t\tparams: mockMetricsQueryParams([]string{\"service1\"}, false),\n\t\t\tresult: createTestSearchResult(false),\n\t\t\texpected: []*metrics.Metric{\n\t\t\t\t{\n\t\t\t\t\tLabels: []*metrics.Label{\n\t\t\t\t\t\t{Name: \"service_name\", Value: \"service1\"},\n\t\t\t\t\t},\n\t\t\t\t\tMetricPoints: []*metrics.MetricPoint{\n\t\t\t\t\t\tcreateEpochGaugePoint(1.23),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"grouped by operation\",\n\t\t\tparams: mockMetricsQueryParams([]string{\"service1\"}, true),\n\t\t\tresult: createTestSearchResult(true),\n\t\t\texpected: []*metrics.Metric{\n\t\t\t\t{\n\t\t\t\t\tLabels: []*metrics.Label{\n\t\t\t\t\t\t{Name: \"service_name\", Value: \"service1\"},\n\t\t\t\t\t\t{Name: \"operation\", Value: \"op1\"},\n\t\t\t\t\t},\n\t\t\t\t\tMetricPoints: []*metrics.MetricPoint{\n\t\t\t\t\t\tcreateEpochGaugePoint(1.23),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttranslator := mockTranslator()\n\t\t\tgot, err := translator.toDomainMetrics(tt.params, tt.result)\n\t\t\tif tt.err != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, tt.err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tt.expected, got)\n\t\t})\n\t}\n}\n\nfunc TestToDomainMetrics_ErrorCases(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tparams MetricsQueryParams\n\t\tresult *elastic.SearchResult\n\t\terrMsg string\n\t}{\n\t\t{\n\t\t\tname:   \"missing terms aggregation when group by operation\",\n\t\t\tparams: mockMetricsQueryParams([]string{\"service1\"}, true),\n\t\t\tresult: &elastic.SearchResult{\n\t\t\t\tAggregations: make(elastic.Aggregations), // Empty aggregations\n\t\t\t},\n\t\t\terrMsg: \"results_buckets aggregation not found\",\n\t\t},\n\t\t{\n\t\t\tname:   \"bucket key not string\",\n\t\t\tparams: mockMetricsQueryParams([]string{\"service1\"}, true),\n\t\t\tresult: createTestSearchResultWithNonStringKey(),\n\t\t\terrMsg: \"bucket key is not a string\",\n\t\t},\n\t\t{\n\t\t\tname:   \"missing date histogram in operation bucket\",\n\t\t\tparams: mockMetricsQueryParams([]string{\"service1\"}, true),\n\t\t\tresult: createTestSearchResultMissingDateHistogram(),\n\t\t\terrMsg: \"date_histogram aggregation not found in bucket\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttranslator := mockTranslator()\n\t\t\t_, err := translator.toDomainMetrics(tt.params, tt.result)\n\t\t\trequire.Error(t, err)\n\t\t\tassert.Contains(t, err.Error(), tt.errMsg)\n\t\t})\n\t}\n}\n\nfunc createEpochGaugePoint(value float64) *metrics.MetricPoint {\n\treturn &metrics.MetricPoint{\n\t\tValue: &metrics.MetricPoint_GaugeValue{\n\t\t\tGaugeValue: &metrics.GaugeValue{\n\t\t\t\tValue: &metrics.GaugeValue_DoubleValue{DoubleValue: value},\n\t\t\t},\n\t\t},\n\t\tTimestamp: mustTimestampProto(time.Unix(0, 0)),\n\t}\n}\n\n// mockMetricsQueryParams creates a MetricsQueryParams struct for testing.\nfunc mockMetricsQueryParams(serviceNames []string, groupByOp bool) MetricsQueryParams {\n\treturn MetricsQueryParams{\n\t\tmetricName: \"test_metric\",\n\t\tmetricDesc: \"test description\",\n\t\tBaseQueryParameters: metricstore.BaseQueryParameters{\n\t\t\tServiceNames:     serviceNames,\n\t\t\tGroupByOperation: groupByOp,\n\t\t},\n\t}\n}\n\nfunc mockTranslator() Translator {\n\tbucketsToPointsFunc := func(_ []*elastic.AggregationBucketHistogramItem) []*Pair {\n\t\treturn []*Pair{{TimeStamp: 0, Value: 1.23}}\n\t}\n\treturn NewTranslator(bucketsToPointsFunc)\n}\n\n// createTestSearchResultWithNonStringKey creates an Elasticsearch SearchResult\n// where the bucket key for operation is an integer, causing a type error.\nfunc createTestSearchResultWithNonStringKey() *elastic.SearchResult {\n\trawAggregation := json.RawMessage(`{\n\t\t\"buckets\": [{\n\t\t\t\"key\": 12345,\n\t\t\t\"doc_count\": 10,\n\t\t\t\"date_histogram\": {\n\t\t\t\t\"buckets\": [{\n\t\t\t\t\t\"key\": 123456,\n\t\t\t\t\t\"doc_count\": 5,\n\t\t\t\t\t\"results\": {\"value\": 1.23}\n\t\t\t\t}]\n\t\t\t}\n\t\t}]\n\t}`)\n\n\taggs := make(elastic.Aggregations)\n\taggs[aggName] = rawAggregation\n\n\treturn &elastic.SearchResult{\n\t\tAggregations: aggs,\n\t}\n}\n\n// createTestSearchResultMissingDateHistogram creates an Elasticsearch SearchResult\n// where an operation bucket is missing the expected date_histogram aggregation.\nfunc createTestSearchResultMissingDateHistogram() *elastic.SearchResult {\n\trawAggregation := json.RawMessage(`{\n\t\t\"buckets\": [{\n\t\t\t\"key\": \"op1\",\n\t\t\t\"doc_count\": 10\n\t\t}]\n\t}`)\n\n\taggs := make(elastic.Aggregations)\n\taggs[aggName] = rawAggregation\n\n\treturn &elastic.SearchResult{\n\t\tAggregations: aggs,\n\t}\n}\n\n// createTestSearchResult creates a well-formed Elasticsearch SearchResult\n// for testing successful conversions, with or without operation grouping.\nfunc createTestSearchResult(groupByOperation bool) *elastic.SearchResult {\n\tvar rawAggregation json.RawMessage\n\n\tif groupByOperation {\n\t\trawAggregation = json.RawMessage(`{\n\t\t\t\"buckets\": [{\n\t\t\t\t\"key\": \"op1\",\n\t\t\t\t\"doc_count\": 10,\n\t\t\t\t\"date_histogram\": {\n\t\t\t\t\t\"buckets\": [{\n\t\t\t\t\t\t\"key_as_string\": \"123456\",\n\t\t\t\t\t\t\"key\": 123456,\n\t\t\t\t\t\t\"doc_count\": 5,\n\t\t\t\t\t\t\"cumulative_requests\": {\n\t\t\t\t\t\t\t\"value\": 1.23\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\t\t\t}]\n\t\t}`)\n\t} else {\n\t\trawAggregation = json.RawMessage(`{\n\t\t\t\"buckets\": [{\n\t\t\t\t\"key_as_string\": \"123456\",\n\t\t\t\t\"key\": 123456,\n\t\t\t\t\"doc_count\": 5,\n\t\t\t\t\"cumulative_requests\": {\n\t\t\t\t\t\"value\": 1.23\n\t\t\t\t}\n\t\t\t}]\n\t\t}`)\n\t}\n\n\taggs := make(elastic.Aggregations)\n\taggs[aggName] = rawAggregation\n\n\treturn &elastic.SearchResult{\n\t\tAggregations: aggs,\n\t}\n}\n\nfunc mustTimestampProto(t time.Time) *types.Timestamp {\n\tts, err := types.TimestampProto(t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ts\n}\n"
  },
  {
    "path": "internal/storage/metricstore/factory.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nconst (\n\tprometheusStorageType = \"prometheus\"\n)\n\n// AllStorageTypes defines all available storage backends.\nvar AllStorageTypes = []string{prometheusStorageType}\n"
  },
  {
    "path": "internal/storage/metricstore/factory_config.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nconst (\n\t// StorageTypeEnvVar is the name of the env var that defines the type of backend used for metrics storage.\n\tStorageTypeEnvVar = \"METRICS_STORAGE_TYPE\"\n)\n\n// FactoryConfig tells the Factory which types of backends it needs to create for different storage types.\ntype FactoryConfig struct {\n\tMetricsStorageType string\n}\n"
  },
  {
    "path": "internal/storage/metricstore/factory_config_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n"
  },
  {
    "path": "internal/storage/metricstore/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n"
  },
  {
    "path": "internal/storage/metricstore/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/factory.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus\n\nimport (\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\n\tconfig \"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n\tprometheusstore \"github.com/jaegertracing/jaeger/internal/storage/metricstore/prometheus/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore/metricstoremetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\n// Factory implements storage.Factory and creates storage components backed by memory store.\ntype Factory struct {\n\toptions *Options\n\ttelset  telemetry.Settings\n\t// httpAuth is an optional authenticator used to wrap the HTTP RoundTripper for outbound requests to Prometheus.\n\thttpAuth extensionauth.HTTPClient\n}\n\n// NewFactory creates a new Factory.\nfunc NewFactory() *Factory {\n\ttelset := telemetry.NoopSettings()\n\treturn &Factory{\n\t\ttelset:  telset,\n\t\toptions: NewOptions(),\n\t}\n}\n\n// Initialize implements storage.V1MetricStoreFactory.\nfunc (f *Factory) Initialize(telset telemetry.Settings) error {\n\tf.telset = telset\n\treturn nil\n}\n\n// CreateMetricsReader implements storage.V1MetricStoreFactory.\nfunc (f *Factory) CreateMetricsReader() (metricstore.Reader, error) {\n\tmr, err := prometheusstore.NewMetricsReader(f.options.Configuration, f.telset.Logger, f.telset.TracerProvider, f.httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn metricstoremetrics.NewReaderDecorator(mr, f.telset.Metrics), nil\n}\n\n// NewFactoryWithConfig creates a new Factory with configuration and optional HTTP authenticator.\n// Pass nil for httpAuth if authentication is not required.\nfunc NewFactoryWithConfig(\n\tcfg config.Configuration,\n\ttelset telemetry.Settings,\n\thttpAuth extensionauth.HTTPClient,\n) (*Factory, error) {\n\tif err := cfg.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\tf := NewFactory()\n\tf.options = &Options{\n\t\tConfiguration: cfg,\n\t}\n\tf.httpAuth = httpAuth\n\terr := f.Initialize(telset)\n\treturn f, err\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/factory_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nvar _ storage.MetricStoreFactory = new(Factory)\n\nfunc TestPrometheusFactory(t *testing.T) {\n\tf := NewFactory()\n\trequire.NoError(t, f.Initialize(telemetry.NoopSettings()))\n\tassert.NotNil(t, f.telset)\n\n\tlistener, err := net.Listen(\"tcp\", \"localhost:\")\n\trequire.NoError(t, err)\n\tassert.NotNil(t, listener)\n\tdefer listener.Close()\n\n\tf.options.ServerURL = \"http://\" + listener.Addr().String()\n\treader, err := f.CreateMetricsReader()\n\n\trequire.NoError(t, err)\n\tassert.NotNil(t, reader)\n}\n\nfunc TestCreateMetricsReaderError(t *testing.T) {\n\tf := NewFactory()\n\tf.options.TLS.CAFile = \"/does/not/exist\"\n\trequire.NoError(t, f.Initialize(telemetry.NoopSettings()))\n\treader, err := f.CreateMetricsReader()\n\trequire.Error(t, err)\n\trequire.Nil(t, reader)\n}\n\nfunc TestWithDefaultConfiguration(t *testing.T) {\n\tf := NewFactory()\n\tassert.Equal(t, \"http://localhost:9090\", f.options.ServerURL)\n\tassert.Equal(t, 30*time.Second, f.options.ConnectTimeout)\n\n\tassert.Equal(t, \"traces_span_metrics\", f.options.MetricNamespace)\n\tassert.Equal(t, \"ms\", f.options.LatencyUnit)\n}\n\nfunc TestWithConfiguration(t *testing.T) {\n\tt.Run(\"with custom configuration and no space in token file path\", func(t *testing.T) {\n\t\tcfg := promcfg.Configuration{\n\t\t\tServerURL:                \"http://localhost:1234\",\n\t\t\tConnectTimeout:           5 * time.Second,\n\t\t\tTokenFilePath:            \"test/test_file.txt\",\n\t\t\tTokenOverrideFromContext: false,\n\t\t}\n\t\tf, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"http://localhost:1234\", f.options.ServerURL)\n\t\tassert.Equal(t, 5*time.Second, f.options.ConnectTimeout)\n\t\tassert.Equal(t, \"test/test_file.txt\", f.options.TokenFilePath)\n\t\tassert.False(t, f.options.TokenOverrideFromContext)\n\t})\n\tt.Run(\"with space in token file path\", func(t *testing.T) {\n\t\tcfg := promcfg.Configuration{\n\t\t\tServerURL:     \"http://localhost:9090\",\n\t\t\tTokenFilePath: \"test/ test file.txt\",\n\t\t}\n\t\tf, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"test/ test file.txt\", f.options.TokenFilePath)\n\t})\n\tt.Run(\"with custom configuration of prometheus.query\", func(t *testing.T) {\n\t\tcfg := promcfg.Configuration{\n\t\t\tServerURL:       \"http://localhost:9090\",\n\t\t\tMetricNamespace: \"mynamespace\",\n\t\t\tLatencyUnit:     \"ms\",\n\t\t}\n\t\tf, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"mynamespace\", f.options.MetricNamespace)\n\t\tassert.Equal(t, \"ms\", f.options.LatencyUnit)\n\t})\n\tt.Run(\"with invalid prometheus.query.duration-unit\", func(t *testing.T) {\n\t\tcfg := promcfg.Configuration{\n\t\t\tServerURL:   \"http://localhost:9090\",\n\t\t\tLatencyUnit: \"milliseconds\",\n\t\t}\n\t\t// NewFactoryWithConfig should validate and reject invalid latency unit\n\t\t// However, the validation is currently not implemented in Configuration.Validate()\n\t\t// So this test now just creates the factory successfully\n\t\tf, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"milliseconds\", f.options.LatencyUnit)\n\t})\n}\n\nfunc TestEmptyFactoryConfig(t *testing.T) {\n\tcfg := promcfg.Configuration{}\n\t_, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\trequire.Error(t, err)\n}\n\nfunc TestFactoryConfig(t *testing.T) {\n\tcfg := promcfg.Configuration{\n\t\tServerURL: \"localhost:1234\",\n\t}\n\t_, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\trequire.NoError(t, err)\n}\n\nfunc TestNewFactoryWithConfigAndAuth(t *testing.T) {\n\tlistener, err := net.Listen(\"tcp\", \"localhost:\")\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tcfg := promcfg.Configuration{\n\t\tServerURL: \"http://\" + listener.Addr().String(),\n\t}\n\n\tmockAuth := &mockHTTPAuthenticator{}\n\n\tfactory, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), mockAuth)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\n\t// Verify the factory can create a metrics reader\n\treader, err := factory.CreateMetricsReader()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, reader)\n\trequire.True(t, mockAuth.called, \"HTTP authenticator should have been called during reader creation\")\n}\n\nfunc TestNewFactoryWithConfigAndAuth_NilAuthenticator(t *testing.T) {\n\tlistener, err := net.Listen(\"tcp\", \"localhost:\")\n\trequire.NoError(t, err)\n\tdefer listener.Close()\n\n\tcfg := promcfg.Configuration{\n\t\tServerURL: \"http://\" + listener.Addr().String(),\n\t}\n\n\t// Should work fine with nil authenticator (backward compatibility)\n\tfactory, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), nil)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, factory)\n\n\treader, err := factory.CreateMetricsReader()\n\trequire.NoError(t, err)\n\trequire.NotNil(t, reader)\n}\n\nfunc TestNewFactoryWithConfigAndAuth_EmptyServerURL(t *testing.T) {\n\tcfg := promcfg.Configuration{\n\t\tServerURL: \"\", // Empty URL should fail\n\t}\n\n\tmockAuth := &mockHTTPAuthenticator{}\n\n\tfactory, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), mockAuth)\n\trequire.Error(t, err)\n\trequire.Nil(t, factory)\n}\n\nfunc TestNewFactoryWithConfigAndAuth_InvalidTLS(t *testing.T) {\n\tcfg := promcfg.Configuration{\n\t\tServerURL: \"https://localhost:9090\",\n\t}\n\tcfg.TLS.CAFile = \"/does/not/exist\"\n\n\tmockAuth := &mockHTTPAuthenticator{}\n\n\tfactory, err := NewFactoryWithConfig(cfg, telemetry.NoopSettings(), mockAuth)\n\trequire.NoError(t, err) // Factory creation succeeds\n\trequire.NotNil(t, factory)\n\n\t// But creating reader should fail due to bad TLS config\n\treader, err := factory.CreateMetricsReader()\n\trequire.Error(t, err)\n\trequire.Nil(t, reader)\n}\n\n// Mock HTTP authenticator for testing\ntype mockHTTPAuthenticator struct {\n\tcalled bool\n}\n\nfunc (m *mockHTTPAuthenticator) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {\n\tm.called = true\n\treturn &mockRoundTripper{base: base}, nil\n}\n\n// Mock RoundTripper for testing\ntype mockRoundTripper struct {\n\tbase http.RoundTripper\n}\n\nfunc (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\t// Add mock authentication header\n\treq.Header.Set(\"Authorization\", \"Bearer test-token\")\n\tif m.base != nil {\n\t\treturn m.base.RoundTrip(req)\n\t}\n\treturn &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/dbmodel/to_domain.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gogo/protobuf/types\"\n\t\"github.com/prometheus/common/model\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n)\n\n// Translator translates Prometheus's metrics model to Jaeger's.\ntype Translator struct {\n\tlabelMap map[string]string\n}\n\n// New returns a new Translator.\nfunc New(spanNameLabel string) Translator {\n\treturn Translator{\n\t\t// \"operation\" is the label name that Jaeger UI expects.\n\t\tlabelMap: map[string]string{spanNameLabel: \"operation\"},\n\t}\n}\n\n// ToDomainMetricsFamily converts Prometheus' representation of metrics query results to Jaeger's.\nfunc (d Translator) ToDomainMetricsFamily(name, description string, mv model.Value) (*metrics.MetricFamily, error) {\n\tif mv.Type() != model.ValMatrix {\n\t\treturn &metrics.MetricFamily{}, fmt.Errorf(\"unexpected metrics ValueType: %s\", mv.Type())\n\t}\n\treturn &metrics.MetricFamily{\n\t\tName:    name,\n\t\tType:    metrics.MetricType_GAUGE,\n\t\tHelp:    description,\n\t\tMetrics: d.toDomainMetrics(mv.(model.Matrix)),\n\t}, nil\n}\n\n// toDomainMetrics converts Prometheus' representation of metrics to Jaeger's.\nfunc (d Translator) toDomainMetrics(matrix model.Matrix) []*metrics.Metric {\n\tms := make([]*metrics.Metric, matrix.Len())\n\tfor i, ss := range matrix {\n\t\tms[i] = &metrics.Metric{\n\t\t\tLabels:       d.toDomainLabels(ss.Metric),\n\t\t\tMetricPoints: toDomainMetricPoints(ss.Values),\n\t\t}\n\t}\n\treturn ms\n}\n\n// toDomainLabels converts Prometheus' representation of metric labels to Jaeger's.\nfunc (d Translator) toDomainLabels(promLabels model.Metric) []*metrics.Label {\n\tlabels := make([]*metrics.Label, len(promLabels))\n\tj := 0\n\tfor k, v := range promLabels {\n\t\tlabelName := string(k)\n\t\tif newLabel, ok := d.labelMap[labelName]; ok {\n\t\t\tlabelName = newLabel\n\t\t}\n\t\tlabels[j] = &metrics.Label{Name: labelName, Value: string(v)}\n\t\tj++\n\t}\n\treturn labels\n}\n\n// toDomainMetricPoints convert's Prometheus' representation of metrics data points to Jaeger's.\nfunc toDomainMetricPoints(promDps []model.SamplePair) []*metrics.MetricPoint {\n\tdomainMps := make([]*metrics.MetricPoint, len(promDps))\n\tfor i, promDp := range promDps {\n\t\tmp := &metrics.MetricPoint{\n\t\t\tTimestamp: toDomainTimestamp(promDp.Timestamp),\n\t\t\tValue:     toDomainMetricPointValue(promDp.Value),\n\t\t}\n\t\tdomainMps[i] = mp\n\t}\n\treturn domainMps\n}\n\n// toDomainTimestamp converts Prometheus' representation of timestamps to Jaeger's.\nfunc toDomainTimestamp(timeMs model.Time) *types.Timestamp {\n\treturn &types.Timestamp{\n\t\tSeconds: int64(timeMs / 1000),\n\t\tNanos:   int32((timeMs % 1000) * 1_000_000),\n\t}\n}\n\n// toDomainMetricPointValue converts Prometheus' representation of a double gauge value to Jaeger's.\n// The gauge metric type is used because latency, call and error rates metrics do not consist of monotonically\n// increasing values; rather, they are a series of any positive floating number which can fluctuate in any\n// direction over time.\nfunc toDomainMetricPointValue(promVal model.SampleValue) *metrics.MetricPoint_GaugeValue {\n\treturn &metrics.MetricPoint_GaugeValue{\n\t\tGaugeValue: &metrics.GaugeValue{\n\t\t\tValue: &metrics.GaugeValue_DoubleValue{DoubleValue: float64(promVal)},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/dbmodel/to_domain_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/types\"\n\t\"github.com/prometheus/common/model\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestToDomainMetricsFamily(t *testing.T) {\n\tpromMetrics := model.Matrix{}\n\tnowSec := time.Now().Unix()\n\tpromMetrics = append(promMetrics, &model.SampleStream{\n\t\tMetric: map[model.LabelName]model.LabelValue{\"label_key\": \"label_value\", \"span_name\": \"span_name_value\"},\n\t\tValues: []model.SamplePair{\n\t\t\t{Timestamp: model.Time(nowSec * 1000), Value: 1234},\n\t\t},\n\t})\n\ttranslator := New(\"span_name\")\n\tmf, err := translator.ToDomainMetricsFamily(\"the_metric_name\", \"the_metric_description\", promMetrics)\n\trequire.NoError(t, err)\n\n\tassert.NotEmpty(t, mf)\n\n\tassert.Equal(t, \"the_metric_name\", mf.Name)\n\tassert.Equal(t, \"the_metric_description\", mf.Help)\n\tassert.Equal(t, metrics.MetricType_GAUGE, mf.Type)\n\n\twantMetricLabels := map[string]string{\n\t\t\"label_key\": \"label_value\",\n\t\t\"operation\": \"span_name_value\", // assert the name is translated to a Jaeger-friendly label.\n\t}\n\tassert.Len(t, mf.Metrics, 1)\n\tfor _, ml := range mf.Metrics[0].Labels {\n\t\tv, ok := wantMetricLabels[ml.Name]\n\t\trequire.True(t, ok)\n\t\tassert.Equal(t, v, ml.Value)\n\t\tdelete(wantMetricLabels, ml.Name)\n\t}\n\tassert.Empty(t, wantMetricLabels)\n\n\twantMpValue := &metrics.MetricPoint_GaugeValue{\n\t\tGaugeValue: &metrics.GaugeValue{\n\t\t\tValue: &metrics.GaugeValue_DoubleValue{\n\t\t\t\tDoubleValue: 1234,\n\t\t\t},\n\t\t},\n\t}\n\tassert.Equal(t, []*metrics.MetricPoint{{Timestamp: &types.Timestamp{Seconds: nowSec}, Value: wantMpValue}}, mf.Metrics[0].MetricPoints)\n}\n\nfunc TestUnexpectedMetricsFamilyType(t *testing.T) {\n\tpromMetrics := model.Vector{}\n\ttranslator := New(\"span_name\")\n\tmf, err := translator.ToDomainMetricsFamily(\"the_metric_name\", \"the_metric_description\", promMetrics)\n\n\tassert.NotNil(t, mf)\n\tassert.Empty(t, mf)\n\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, \"unexpected metrics ValueType: vector\")\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/reader.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/prometheus/client_golang/api\"\n\tpromapi \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth\"\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n\tconfig \"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/metricstore/prometheus/metricstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nconst (\n\tminStep = time.Millisecond\n)\n\ntype (\n\t// MetricsReader is a Prometheus metrics reader.\n\tMetricsReader struct {\n\t\tclient promapi.API\n\t\tlogger *zap.Logger\n\t\ttracer trace.Tracer\n\n\t\tmetricsTranslator dbmodel.Translator\n\t\tlatencyMetricName string\n\t\tcallsMetricName   string\n\t\toperationLabel    string // name of the attribute that contains span name / operation\n\t}\n\n\tpromQueryParams struct {\n\t\tgroupBy        string\n\t\tspanKindFilter string\n\t\tserviceFilter  string\n\t\trate           string\n\t}\n\n\tmetricsQueryParams struct {\n\t\tmetricstore.BaseQueryParameters\n\t\tgroupByHistBucket bool\n\t\tmetricName        string\n\t\tmetricDesc        string\n\t\tbuildPromQuery    func(p promQueryParams) string\n\t}\n\n\tpromClient struct {\n\t\tapi.Client\n\t\textraParams map[string]string\n\t}\n)\n\n// URL decorator enables adding additional query parameters to the request sent to prometheus backend\nfunc (p promClient) URL(ep string, args map[string]string) *url.URL {\n\tu := p.Client.URL(ep, args)\n\n\tquery := u.Query()\n\tfor k, v := range p.extraParams {\n\t\tquery.Add(k, v)\n\t}\n\tu.RawQuery = query.Encode()\n\n\treturn u\n}\n\nfunc createPromClient(cfg config.Configuration, httpAuth extensionauth.HTTPClient) (api.Client, error) {\n\troundTripper, err := getHTTPRoundTripper(&cfg, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpromConfig := api.Config{\n\t\tAddress:      cfg.ServerURL,\n\t\tRoundTripper: roundTripper,\n\t}\n\n\tclient, err := api.NewClient(promConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn promClient{\n\t\tClient:      client,\n\t\textraParams: cfg.ExtraQueryParams,\n\t}, nil\n}\n\n// NewMetricsReader returns a new MetricsReader with optional HTTP authentication.\n// Pass nil for httpAuth if authentication is not required.\nfunc NewMetricsReader(cfg config.Configuration, logger *zap.Logger, tracer trace.TracerProvider, httpAuth extensionauth.HTTPClient) (*MetricsReader, error) {\n\tconst operationLabel = \"span_name\"\n\n\tpromClient, err := createPromClient(cfg, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmr := &MetricsReader{\n\t\tclient: promapi.NewAPI(promClient),\n\t\tlogger: logger,\n\t\ttracer: tracer.Tracer(\"prom-metrics-reader\"),\n\n\t\tmetricsTranslator: dbmodel.New(operationLabel),\n\t\tcallsMetricName:   buildFullCallsMetricName(cfg),\n\t\tlatencyMetricName: buildFullLatencyMetricName(cfg),\n\t\toperationLabel:    operationLabel,\n\t}\n\n\tlogger.Info(\"Prometheus reader initialized\", zap.String(\"addr\", cfg.ServerURL))\n\treturn mr, nil\n}\n\n// GetLatencies gets the latency metrics for the given set of latency query parameters.\nfunc (m MetricsReader) GetLatencies(ctx context.Context, requestParams *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error) {\n\tmetricsParams := metricsQueryParams{\n\t\tBaseQueryParameters: requestParams.BaseQueryParameters,\n\t\tgroupByHistBucket:   true,\n\t\tmetricName:          \"service_latencies\",\n\t\tmetricDesc:          fmt.Sprintf(\"%.2fth quantile latency, grouped by service\", requestParams.Quantile),\n\t\tbuildPromQuery: func(p promQueryParams) string {\n\t\t\treturn fmt.Sprintf(\n\t\t\t\t// Note: p.spanKindFilter can be \"\"; trailing commas are okay within a timeseries selection.\n\t\t\t\t`histogram_quantile(%.2f, sum(rate(%s_bucket{service_name =~ %q, %s}[%s])) by (%s))`,\n\t\t\t\trequestParams.Quantile,\n\t\t\t\tm.latencyMetricName,\n\t\t\t\tp.serviceFilter,\n\t\t\t\tp.spanKindFilter,\n\t\t\t\tp.rate,\n\t\t\t\tp.groupBy,\n\t\t\t)\n\t\t},\n\t}\n\treturn m.executeQuery(ctx, metricsParams)\n}\n\nfunc buildFullLatencyMetricName(cfg config.Configuration) string {\n\tmetricName := \"duration\"\n\n\tif cfg.MetricNamespace != \"\" {\n\t\tmetricName = cfg.MetricNamespace + \"_\" + metricName\n\t}\n\n\tif !cfg.NormalizeDuration {\n\t\treturn metricName\n\t}\n\n\t// The long names are automatically appended to the metric name by OTEL's prometheus exporters and are defined in:\n\t//   https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/translator/prometheus#metric-name\n\tshortToLongName := map[string]string{\"ms\": \"milliseconds\", \"s\": \"seconds\"}\n\tlname, ok := shortToLongName[cfg.LatencyUnit]\n\tif !ok {\n\t\tpanic(\"programming error: unknown latency unit: \" + cfg.LatencyUnit)\n\t}\n\treturn metricName + \"_\" + lname\n}\n\n// GetCallRates gets the call rate metrics for the given set of call rate query parameters.\nfunc (m MetricsReader) GetCallRates(ctx context.Context, requestParams *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error) {\n\tmetricsParams := metricsQueryParams{\n\t\tBaseQueryParameters: requestParams.BaseQueryParameters,\n\t\tmetricName:          \"service_call_rate\",\n\t\tmetricDesc:          \"calls/sec, grouped by service\",\n\t\tbuildPromQuery: func(p promQueryParams) string {\n\t\t\treturn fmt.Sprintf(\n\t\t\t\t// Note: p.spanKindFilter can be \"\"; trailing commas are okay within a timeseries selection.\n\t\t\t\t`sum(rate(%s{service_name =~ %q, %s}[%s])) by (%s)`,\n\t\t\t\tm.callsMetricName,\n\t\t\t\tp.serviceFilter,\n\t\t\t\tp.spanKindFilter,\n\t\t\t\tp.rate,\n\t\t\t\tp.groupBy,\n\t\t\t)\n\t\t},\n\t}\n\treturn m.executeQuery(ctx, metricsParams)\n}\n\nfunc buildFullCallsMetricName(cfg config.Configuration) string {\n\tmetricName := \"calls\"\n\tif cfg.MetricNamespace != \"\" {\n\t\tmetricName = cfg.MetricNamespace + \"_\" + metricName\n\t}\n\n\tif !cfg.NormalizeCalls {\n\t\treturn metricName\n\t}\n\n\treturn metricName + \"_total\"\n}\n\n// GetErrorRates gets the error rate metrics for the given set of error rate query parameters.\nfunc (m MetricsReader) GetErrorRates(ctx context.Context, requestParams *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error) {\n\tmetricsParams := metricsQueryParams{\n\t\tBaseQueryParameters: requestParams.BaseQueryParameters,\n\t\tmetricName:          \"service_error_rate\",\n\t\tmetricDesc:          \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\tbuildPromQuery: func(p promQueryParams) string {\n\t\t\treturn fmt.Sprintf(\n\t\t\t\t// Note: p.spanKindFilter can be \"\"; trailing commas are okay within a timeseries selection.\n\t\t\t\t`sum(rate(%s{service_name =~ %q, status_code = \"STATUS_CODE_ERROR\", %s}[%s])) by (%s) / sum(rate(%s{service_name =~ %q, %s}[%s])) by (%s)`,\n\t\t\t\tm.callsMetricName, p.serviceFilter, p.spanKindFilter, p.rate, p.groupBy,\n\t\t\t\tm.callsMetricName, p.serviceFilter, p.spanKindFilter, p.rate, p.groupBy,\n\t\t\t)\n\t\t},\n\t}\n\terrorMetrics, err := m.executeQuery(ctx, metricsParams)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed getting error metrics: %w\", err)\n\t}\n\t// Non-zero error rates are available.\n\tif len(errorMetrics.Metrics) > 0 {\n\t\treturn errorMetrics, nil\n\t}\n\n\t// Check for the presence of call rate metrics to differentiate the absence of error rate from\n\t// the absence of call rate metrics altogether.\n\tcallMetrics, err := m.GetCallRates(ctx, &metricstore.CallRateQueryParameters{BaseQueryParameters: requestParams.BaseQueryParameters})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed getting call metrics: %w\", err)\n\t}\n\t// No call rate metrics are available, and by association, means no error rate metrics are available.\n\tif len(callMetrics.Metrics) == 0 {\n\t\treturn errorMetrics, nil\n\t}\n\n\t// Non-zero call rate metrics are available, which implies that there are just no errors, so we report a zero error rate.\n\tzeroErrorMetrics := make([]*metrics.Metric, 0, len(callMetrics.Metrics))\n\tfor _, cm := range callMetrics.Metrics {\n\t\tzm := *cm\n\t\tfor i := 0; i < len(zm.MetricPoints); i++ {\n\t\t\tzm.MetricPoints[i].Value = &metrics.MetricPoint_GaugeValue{GaugeValue: &metrics.GaugeValue{Value: &metrics.GaugeValue_DoubleValue{DoubleValue: 0.0}}}\n\t\t}\n\t\tzeroErrorMetrics = append(zeroErrorMetrics, &zm)\n\t}\n\n\terrorMetrics.Metrics = zeroErrorMetrics\n\treturn errorMetrics, nil\n}\n\n// GetMinStepDuration gets the minimum step duration (the smallest possible duration between two data points in a time series) supported.\nfunc (MetricsReader) GetMinStepDuration(_ context.Context, _ *metricstore.MinStepDurationQueryParameters) (time.Duration, error) {\n\treturn minStep, nil\n}\n\n// executeQuery executes a query against a Prometheus-compliant metrics backend.\nfunc (m MetricsReader) executeQuery(ctx context.Context, p metricsQueryParams) (*metrics.MetricFamily, error) {\n\tif p.GroupByOperation {\n\t\tp.metricName = strings.Replace(p.metricName, \"service\", \"service_operation\", 1)\n\t\tp.metricDesc += \" & operation\"\n\t}\n\tpromQuery := m.buildPromQuery(p)\n\n\tctx, span := startSpanForQuery(ctx, p.metricName, promQuery, m.tracer)\n\tdefer span.End()\n\n\tqueryRange := promapi.Range{\n\t\tStart: p.EndTime.Add(-1 * *p.Lookback),\n\t\tEnd:   *p.EndTime,\n\t\tStep:  *p.Step,\n\t}\n\n\tmv, warnings, err := m.client.QueryRange(ctx, promQuery, queryRange)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed executing metrics query: %w\", err)\n\t\tlogErrorToSpan(span, err)\n\t\treturn &metrics.MetricFamily{}, err\n\t}\n\tif len(warnings) > 0 {\n\t\tm.logger.Warn(\"Warnings detected on Prometheus query\", zap.Any(\"warnings\", warnings), zap.String(\"query\", promQuery), zap.Any(\"range\", queryRange))\n\t}\n\n\tm.logger.Debug(\"Prometheus query results\", zap.String(\"results\", mv.String()), zap.String(\"query\", promQuery), zap.Any(\"range\", queryRange))\n\n\treturn m.metricsTranslator.ToDomainMetricsFamily(\n\t\tp.metricName,\n\t\tp.metricDesc,\n\t\tmv,\n\t)\n}\n\nfunc (m MetricsReader) buildPromQuery(metricsParams metricsQueryParams) string {\n\tgroupBy := []string{\"service_name\"}\n\tif metricsParams.GroupByOperation {\n\t\tgroupBy = append(groupBy, m.operationLabel)\n\t}\n\tif metricsParams.groupByHistBucket {\n\t\t// Group by the bucket value (\"le\" => \"less than or equal to\").\n\t\tgroupBy = append(groupBy, \"le\")\n\t}\n\n\tspanKindFilter := \"\"\n\tif len(metricsParams.SpanKinds) > 0 {\n\t\tspanKindFilter = fmt.Sprintf(`span_kind =~ %q`, strings.Join(metricsParams.SpanKinds, \"|\"))\n\t}\n\tpromParams := promQueryParams{\n\t\tserviceFilter:  strings.Join(metricsParams.ServiceNames, \"|\"),\n\t\tspanKindFilter: spanKindFilter,\n\t\trate:           promqlDurationString(metricsParams.RatePer),\n\t\tgroupBy:        strings.Join(groupBy, \",\"),\n\t}\n\treturn metricsParams.buildPromQuery(promParams)\n}\n\n// promqlDurationString formats the duration string to be promQL-compliant.\n// PromQL only accepts \"single-unit\" durations like \"30s\", \"1m\", \"1h\"; not \"1h5s\" or \"1m0s\".\nfunc promqlDurationString(d *time.Duration) string {\n\tvar b []byte\n\tfor _, c := range d.String() {\n\t\tb = append(b, byte(c)) //nolint:gosec // G115 - duration strings are ASCII\n\t\tif unicode.IsLetter(c) {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn string(b)\n}\n\nfunc startSpanForQuery(ctx context.Context, metricName, query string, tp trace.Tracer) (context.Context, trace.Span) {\n\tctx, span := tp.Start(ctx, metricName)\n\tspan.SetAttributes(\n\t\tattribute.Key(otelsemconv.DBQueryTextKey).String(query),\n\t\tattribute.Key(otelsemconv.DBSystemKey).String(\"prometheus\"),\n\t\tattribute.Key(\"component\").String(\"promql\"),\n\t)\n\treturn ctx, span\n}\n\nfunc logErrorToSpan(span trace.Span, err error) {\n\tspan.RecordError(err)\n\tspan.SetStatus(codes.Error, err.Error())\n}\n\nfunc getHTTPRoundTripper(c *config.Configuration, httpAuth extensionauth.HTTPClient) (rt http.RoundTripper, err error) {\n\tctlsConfig, err := c.TLS.LoadTLSConfig(context.Background())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// KeepAlive and TLSHandshake timeouts are kept to existing Prometheus client's\n\t// DefaultRoundTripper to simplify user configuration and may be made configurable when required.\n\thttpTransport := &http.Transport{\n\t\tProxy: http.ProxyFromEnvironment,\n\t\tDialContext: (&net.Dialer{\n\t\t\tTimeout:   c.ConnectTimeout,\n\t\t\tKeepAlive: 30 * time.Second,\n\t\t}).DialContext,\n\t\tTLSHandshakeTimeout: 10 * time.Second,\n\t\tTLSClientConfig:     ctlsConfig,\n\t}\n\t// dynamic token loader with interval-based caching\n\tvar tokenFn func() string\n\tif c.TokenFilePath != \"\" {\n\t\tvar err error\n\t\ttokenFn, err = auth.TokenProvider(\n\t\t\tc.TokenFilePath,\n\t\t\t10*time.Second,\n\t\t\tnil,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\t// Only set FromCtxFn if token override from context is enabled\n\tvar fromCtxFn func(context.Context) (string, bool)\n\tif c.TokenOverrideFromContext {\n\t\tfromCtxFn = bearertoken.GetBearerToken\n\t}\n\tbase := &auth.RoundTripper{\n\t\tTransport: httpTransport,\n\t\tAuths: []auth.Method{\n\t\t\t{\n\t\t\t\tScheme:  \"Bearer\",\n\t\t\t\tTokenFn: tokenFn,\n\t\t\t\tFromCtx: fromCtxFn,\n\t\t\t},\n\t\t},\n\t}\n\tif httpAuth == nil {\n\t\treturn base, nil\n\t}\n\treturn httpAuth.RoundTripper(base)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/reader_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\t\"go.opentelemetry.io/otel/codes\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/sdk/trace/tracetest\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n\tconfig \"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype (\n\tmetricsTestCase struct {\n\t\tname             string\n\t\tserviceNames     []string\n\t\tspanKinds        []string\n\t\tgroupByOperation bool\n\t\tupdateConfig     func(config.Configuration) config.Configuration\n\t\twantName         string\n\t\twantDescription  string\n\t\twantLabels       map[string]string\n\t\twantPromQlQuery  string\n\t}\n)\n\nconst defaultTimeout = 30 * time.Second\n\n// defaultConfig should consist of the default values for the prometheus.query.* command line options.\nvar defaultConfig = config.Configuration{\n\tMetricNamespace: \"\",\n\tLatencyUnit:     \"ms\",\n}\n\nfunc tracerProvider(t *testing.T) (trace.TracerProvider, *tracetest.InMemoryExporter, func()) {\n\texporter := tracetest.NewInMemoryExporter()\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()),\n\t\tsdktrace.WithSyncer(exporter),\n\t)\n\tcloser := func() {\n\t\trequire.NoError(t, tp.Shutdown(context.Background()))\n\t}\n\treturn tp, exporter, closer\n}\n\nfunc TestNewMetricsReaderValidAddress(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttracer, _, closer := tracerProvider(t)\n\tdefer closer()\n\treader, err := NewMetricsReader(config.Configuration{\n\t\tServerURL:      \"http://localhost:1234\",\n\t\tConnectTimeout: defaultTimeout,\n\t}, logger, tracer, nil)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, reader)\n}\n\nfunc TestNewMetricsReaderInvalidAddress(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttracer, _, closer := tracerProvider(t)\n\tdefer closer()\n\treader, err := NewMetricsReader(config.Configuration{\n\t\tServerURL:      \"\\n\",\n\t\tConnectTimeout: defaultTimeout,\n\t}, logger, tracer, nil)\n\trequire.Error(t, err)\n\tassert.Nil(t, reader)\n}\n\nfunc TestGetMinStepDuration(t *testing.T) {\n\tparams := metricstore.MinStepDurationQueryParameters{}\n\tlogger := zap.NewNop()\n\ttracer, _, closer := tracerProvider(t)\n\tdefer closer()\n\tlistener, err := net.Listen(\"tcp\", \"localhost:\")\n\trequire.NoError(t, err)\n\tassert.NotNil(t, listener)\n\n\treader, err := NewMetricsReader(config.Configuration{\n\t\tServerURL:      \"http://\" + listener.Addr().String(),\n\t\tConnectTimeout: defaultTimeout,\n\t}, logger, tracer, nil)\n\trequire.NoError(t, err)\n\n\tminStep, err := reader.GetMinStepDuration(context.Background(), &params)\n\trequire.NoError(t, err)\n\tassert.Equal(t, time.Millisecond, minStep)\n}\n\nfunc TestMetricsServerError(t *testing.T) {\n\tendTime := time.Now()\n\tlookback := time.Minute\n\tstep := time.Millisecond\n\tratePer := 10 * time.Minute\n\n\tparams := metricstore.CallRateQueryParameters{\n\t\tBaseQueryParameters: metricstore.BaseQueryParameters{\n\t\t\tEndTime:  &endTime,\n\t\t\tLookback: &lookback,\n\t\t\tStep:     &step,\n\t\t\tRatePer:  &ratePer,\n\t\t},\n\t}\n\n\tmockPrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\thttp.Error(w, \"internal server error\", http.StatusInternalServerError)\n\t}))\n\tdefer mockPrometheus.Close()\n\n\tlogger := zap.NewNop()\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\taddress := mockPrometheus.Listener.Addr().String()\n\treader, err := NewMetricsReader(config.Configuration{\n\t\tServerURL:      \"http://\" + address,\n\t\tConnectTimeout: defaultTimeout,\n\t}, logger, tracer, nil)\n\trequire.NoError(t, err)\n\tm, err := reader.GetCallRates(context.Background(), &params)\n\tassert.NotNil(t, m)\n\trequire.ErrorContains(t, err, \"failed executing metrics query\")\n\trequire.Len(t, exp.GetSpans(), 1, \"HTTP request was traced and span reported\")\n\tassert.Equal(t, codes.Error, exp.GetSpans()[0].Status.Code)\n}\n\nfunc TestGetLatencies(t *testing.T) {\n\tfor _, tc := range []metricsTestCase{\n\t\t{\n\t\t\tname:             \"group by service should be reflected in name/description and query group-by\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: false,\n\t\t\twantName:         \"service_latencies\",\n\t\t\twantDescription:  \"0.95th quantile latency, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `histogram_quantile(0.95, sum(rate(duration_bucket{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,le))`,\n\t\t},\n\t\t{\n\t\t\tname:             \"group by service and operation should be reflected in name/description and query group-by\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\twantName:         \"service_operation_latencies\",\n\t\t\twantDescription:  \"0.95th quantile latency, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `histogram_quantile(0.95, sum(rate(duration_bucket{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name,le))`,\n\t\t},\n\t\t{\n\t\t\tname:             \"two services and span kinds result in regex 'or' symbol in query\",\n\t\t\tserviceNames:     []string{\"frontend\", \"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\", \"SPAN_KIND_CLIENT\"},\n\t\t\tgroupByOperation: false,\n\t\t\twantName:         \"service_latencies\",\n\t\t\twantDescription:  \"0.95th quantile latency, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `histogram_quantile(0.95, sum(rate(duration_bucket{service_name =~ \"frontend|emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER|SPAN_KIND_CLIENT\"}[10m])) by (service_name,le))`,\n\t\t},\n\t\t{\n\t\t\tname:             \"enable support for spanmetrics connector with a namespace\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.MetricNamespace = \"span_metrics\"\n\t\t\t\tcfg.LatencyUnit = \"s\"\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_operation_latencies\",\n\t\t\twantDescription: \"0.95th quantile latency, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `histogram_quantile(0.95, sum(rate(span_metrics_duration_bucket{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name,le))`,\n\t\t},\n\t\t{\n\t\t\tname:             \"enable support for spanmetrics connector with normalized metric name\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.NormalizeDuration = true\n\t\t\t\tcfg.LatencyUnit = \"s\"\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_operation_latencies\",\n\t\t\twantDescription: \"0.95th quantile latency, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `histogram_quantile(0.95, sum(rate(duration_seconds_bucket{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name,le))`,\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tparams := metricstore.LatenciesQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(tc),\n\t\t\t\tQuantile:            0.95,\n\t\t\t}\n\t\t\ttracer, exp, closer := tracerProvider(t)\n\t\t\tdefer closer()\n\t\t\tcfg := defaultConfig\n\t\t\tif tc.updateConfig != nil {\n\t\t\t\tcfg = tc.updateConfig(cfg)\n\t\t\t}\n\t\t\treader, mockPrometheus := prepareMetricsReaderAndServer(t, cfg, tc.wantPromQlQuery, nil, tracer)\n\t\t\tdefer mockPrometheus.Close()\n\n\t\t\tm, err := reader.GetLatencies(context.Background(), &params)\n\t\t\trequire.NoError(t, err)\n\t\t\tassertMetrics(t, m, tc.wantLabels, tc.wantName, tc.wantDescription)\n\t\t\tassert.Len(t, exp.GetSpans(), 1, \"HTTP request was traced and span reported\")\n\t\t})\n\t}\n}\n\nfunc TestGetCallRates(t *testing.T) {\n\tfor _, tc := range []metricsTestCase{\n\t\t{\n\t\t\tname:             \"group by service only should be reflected in name/description and query group-by\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: false,\n\t\t\twantName:         \"service_call_rate\",\n\t\t\twantDescription:  \"calls/sec, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"group by service and operation should be reflected in name/description and query group-by\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\twantName:         \"service_operation_call_rate\",\n\t\t\twantDescription:  \"calls/sec, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"two services and span kinds result in regex 'or' symbol in query\",\n\t\t\tserviceNames:     []string{\"frontend\", \"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\", \"SPAN_KIND_CLIENT\"},\n\t\t\tgroupByOperation: false,\n\t\t\twantName:         \"service_call_rate\",\n\t\t\twantDescription:  \"calls/sec, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls{service_name =~ \"frontend|emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER|SPAN_KIND_CLIENT\"}[10m])) by (service_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"enable support for spanmetrics connector with a namespace\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.MetricNamespace = \"span_metrics\"\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_operation_call_rate\",\n\t\t\twantDescription: \"calls/sec, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(span_metrics_calls{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"enable support for spanmetrics connector with normalized metric name\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.NormalizeCalls = true\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_operation_call_rate\",\n\t\t\twantDescription: \"calls/sec, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls_total{service_name =~ \"emailservice\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name)`,\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tparams := metricstore.CallRateQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(tc),\n\t\t\t}\n\t\t\ttracer, exp, closer := tracerProvider(t)\n\t\t\tdefer closer()\n\t\t\tcfg := defaultConfig\n\t\t\tif tc.updateConfig != nil {\n\t\t\t\tcfg = tc.updateConfig(cfg)\n\t\t\t}\n\t\t\treader, mockPrometheus := prepareMetricsReaderAndServer(t, cfg, tc.wantPromQlQuery, nil, tracer)\n\t\t\tdefer mockPrometheus.Close()\n\n\t\t\tm, err := reader.GetCallRates(context.Background(), &params)\n\t\t\trequire.NoError(t, err)\n\t\t\tassertMetrics(t, m, tc.wantLabels, tc.wantName, tc.wantDescription)\n\t\t\tassert.Len(t, exp.GetSpans(), 1, \"HTTP request was traced and span reported\")\n\t\t})\n\t}\n}\n\nfunc TestGetErrorRates(t *testing.T) {\n\tfor _, tc := range []metricsTestCase{\n\t\t{\n\t\t\tname:             \"group by service only should be reflected in name/description and query group-by\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: false,\n\t\t\twantName:         \"service_error_rate\",\n\t\t\twantDescription:  \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name) / ` +\n\t\t\t\t`sum(rate(calls{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"group by service and operation should be reflected in name/description and query group-by\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\twantName:         \"service_operation_error_rate\",\n\t\t\twantDescription:  \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name) / ` +\n\t\t\t\t`sum(rate(calls{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"two services and span kinds result in regex 'or' symbol in query\",\n\t\t\tserviceNames:     []string{\"frontend\", \"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\", \"SPAN_KIND_CLIENT\"},\n\t\t\tgroupByOperation: false,\n\t\t\twantName:         \"service_error_rate\",\n\t\t\twantDescription:  \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls{service_name =~ \"frontend|emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER|SPAN_KIND_CLIENT\"}[10m])) by (service_name) / ` +\n\t\t\t\t`sum(rate(calls{service_name =~ \"frontend|emailservice\", span_kind =~ \"SPAN_KIND_SERVER|SPAN_KIND_CLIENT\"}[10m])) by (service_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"neither metric namespace nor enabling normalized metric names have an impact when spanmetrics connector is not supported\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: false,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.MetricNamespace = \"span_metrics\"\n\t\t\t\tcfg.NormalizeCalls = true\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_error_rate\",\n\t\t\twantDescription: \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(span_metrics_calls_total{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name) / ` +\n\t\t\t\t`sum(rate(span_metrics_calls_total{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"enable support for spanmetrics connector with a metric namespace\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.MetricNamespace = \"span_metrics\"\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_operation_error_rate\",\n\t\t\twantDescription: \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(span_metrics_calls{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name) / ` +\n\t\t\t\t`sum(rate(span_metrics_calls{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name)`,\n\t\t},\n\t\t{\n\t\t\tname:             \"enable support for spanmetrics connector with normalized metric name\",\n\t\t\tserviceNames:     []string{\"emailservice\"},\n\t\t\tspanKinds:        []string{\"SPAN_KIND_SERVER\"},\n\t\t\tgroupByOperation: true,\n\t\t\tupdateConfig: func(cfg config.Configuration) config.Configuration {\n\t\t\t\tcfg.NormalizeCalls = true\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\twantName:        \"service_operation_error_rate\",\n\t\t\twantDescription: \"error rate, computed as a fraction of errors/sec over calls/sec, grouped by service & operation\",\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"operation\":    \"/OrderResult\",\n\t\t\t\t\"service_name\": \"emailservice\",\n\t\t\t},\n\t\t\twantPromQlQuery: `sum(rate(calls_total{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name) / ` +\n\t\t\t\t`sum(rate(calls_total{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name,span_name)`,\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tparams := metricstore.ErrorRateQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(tc),\n\t\t\t}\n\t\t\ttracer, exp, closer := tracerProvider(t)\n\t\t\tdefer closer()\n\t\t\tcfg := defaultConfig\n\t\t\tif tc.updateConfig != nil {\n\t\t\t\tcfg = tc.updateConfig(cfg)\n\t\t\t}\n\t\t\treader, mockPrometheus := prepareMetricsReaderAndServer(t, cfg, tc.wantPromQlQuery, nil, tracer)\n\t\t\tdefer mockPrometheus.Close()\n\n\t\t\tm, err := reader.GetErrorRates(context.Background(), &params)\n\t\t\trequire.NoError(t, err)\n\t\t\tassertMetrics(t, m, tc.wantLabels, tc.wantName, tc.wantDescription)\n\t\t\tassert.Len(t, exp.GetSpans(), 1, \"HTTP request was traced and span reported\")\n\t\t})\n\t}\n}\n\nfunc TestGetErrorRatesZero(t *testing.T) {\n\tparams := metricstore.ErrorRateQueryParameters{\n\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(metricsTestCase{\n\t\t\tserviceNames: []string{\"emailservice\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t}),\n\t}\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\n\tconst (\n\t\tqueryErrorRate = `sum(rate(calls{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name) / ` +\n\t\t\t`sum(rate(calls{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`\n\t\tqueryCallRate = `sum(rate(calls{service_name =~ \"emailservice\", ` +\n\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`\n\t)\n\twantPromQLQueries := []string{queryErrorRate, queryCallRate}\n\tresponses := []string{\"testdata/empty_response.json\", \"testdata/service_datapoint_response.json\"}\n\tvar callCount int\n\n\tmockPrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\tdefer r.Body.Close()\n\n\t\tu, err := url.Parse(\"http://\" + r.Host + r.RequestURI + \"?\" + string(body))\n\t\tif assert.NoError(t, err) {\n\t\t\tq := u.Query()\n\t\t\tpromQuery := q.Get(\"query\")\n\t\t\tassert.Equal(t, wantPromQLQueries[callCount], promQuery)\n\t\t\tsendResponse(t, w, responses[callCount])\n\t\t\tcallCount++\n\t\t}\n\t}))\n\n\tlogger := zap.NewNop()\n\taddress := mockPrometheus.Listener.Addr().String()\n\n\tcfg := defaultConfig\n\tcfg.ServerURL = \"http://\" + address\n\tcfg.ConnectTimeout = defaultTimeout\n\n\treader, err := NewMetricsReader(cfg, logger, tracer, nil)\n\trequire.NoError(t, err)\n\n\tdefer mockPrometheus.Close()\n\n\tm, err := reader.GetErrorRates(context.Background(), &params)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, m.Metrics, 1)\n\tmps := m.Metrics[0].MetricPoints\n\n\trequire.Len(t, mps, 1)\n\n\t// Assert that we essentially zeroed the call rate data point.\n\t// That is, the timestamp is the same as the call rate's data point, but the value is 0.\n\tactualVal := mps[0].Value.(*metrics.MetricPoint_GaugeValue).GaugeValue.Value.(*metrics.GaugeValue_DoubleValue).DoubleValue\n\tassert.Zero(t, actualVal)\n\tassert.Equal(t, int64(1620351786), mps[0].Timestamp.GetSeconds())\n\tassert.Len(t, exp.GetSpans(), 2, \"expected an error rate query and a call rate query to be made\")\n}\n\nfunc TestGetErrorRatesNull(t *testing.T) {\n\tparams := metricstore.ErrorRateQueryParameters{\n\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(metricsTestCase{\n\t\t\tserviceNames: []string{\"emailservice\"},\n\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t}),\n\t}\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\n\tconst (\n\t\tqueryErrorRate = `sum(rate(calls{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name) / ` +\n\t\t\t`sum(rate(calls{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`\n\t\tqueryCallRate = `sum(rate(calls{service_name =~ \"emailservice\", ` +\n\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`\n\t)\n\twantPromQLQueries := []string{queryErrorRate, queryCallRate}\n\tresponses := []string{\"testdata/empty_response.json\", \"testdata/empty_response.json\"}\n\tvar callCount int\n\n\tmockPrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\tdefer r.Body.Close()\n\n\t\tu, err := url.Parse(\"http://\" + r.Host + r.RequestURI + \"?\" + string(body))\n\t\tassert.NoError(t, err)\n\n\t\tq := u.Query()\n\t\tpromQuery := q.Get(\"query\")\n\t\tassert.Equal(t, wantPromQLQueries[callCount], promQuery)\n\t\tsendResponse(t, w, responses[callCount])\n\t\tcallCount++\n\t}))\n\n\tlogger := zap.NewNop()\n\taddress := mockPrometheus.Listener.Addr().String()\n\n\tcfg := defaultConfig\n\tcfg.ServerURL = \"http://\" + address\n\tcfg.ConnectTimeout = defaultTimeout\n\n\treader, err := NewMetricsReader(cfg, logger, tracer, nil)\n\trequire.NoError(t, err)\n\n\tdefer mockPrometheus.Close()\n\n\tm, err := reader.GetErrorRates(context.Background(), &params)\n\trequire.NoError(t, err)\n\tassert.Empty(t, m.Metrics, \"expect no error data available\")\n\tassert.Len(t, exp.GetSpans(), 2, \"expected an error rate query and a call rate query to be made\")\n}\n\nfunc TestGetErrorRatesErrors(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname               string\n\t\tfailErrorRateQuery bool\n\t\tfailCallRateQuery  bool\n\t\twantErr            string\n\t}{\n\t\t{\n\t\t\tname:               \"error rate query failure\",\n\t\t\tfailErrorRateQuery: true,\n\t\t\twantErr:            \"failed getting error metrics: failed executing metrics query: server_error: server error: 500\",\n\t\t},\n\t\t{\n\t\t\tname:              \"call rate query failure\",\n\t\t\tfailCallRateQuery: true,\n\t\t\twantErr:           \"failed getting call metrics: failed executing metrics query: server_error: server error: 500\",\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tparams := metricstore.ErrorRateQueryParameters{\n\t\t\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(metricsTestCase{\n\t\t\t\t\tserviceNames: []string{\"emailservice\"},\n\t\t\t\t\tspanKinds:    []string{\"SPAN_KIND_SERVER\"},\n\t\t\t\t}),\n\t\t\t}\n\t\t\ttracer, _, closer := tracerProvider(t)\n\t\t\tdefer closer()\n\n\t\t\tconst (\n\t\t\t\tqueryErrorRate = `sum(rate(calls{service_name =~ \"emailservice\", status_code = \"STATUS_CODE_ERROR\", ` +\n\t\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name) / ` +\n\t\t\t\t\t`sum(rate(calls{service_name =~ \"emailservice\", span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`\n\t\t\t\tqueryCallRate = `sum(rate(calls{service_name =~ \"emailservice\", ` +\n\t\t\t\t\t`span_kind =~ \"SPAN_KIND_SERVER\"}[10m])) by (service_name)`\n\t\t\t)\n\t\t\twantPromQLQueries := []string{queryErrorRate, queryCallRate}\n\t\t\tresponses := []string{\"testdata/empty_response.json\", \"testdata/service_datapoint_response.json\"}\n\t\t\tvar callCount int\n\n\t\t\tmockPrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tbody, _ := io.ReadAll(r.Body)\n\t\t\t\tdefer r.Body.Close()\n\n\t\t\t\tu, err := url.Parse(\"http://\" + r.Host + r.RequestURI + \"?\" + string(body))\n\t\t\t\tassert.NoError(t, err)\n\n\t\t\t\tq := u.Query()\n\t\t\t\tpromQuery := q.Get(\"query\")\n\t\t\t\tassert.Equal(t, wantPromQLQueries[callCount], promQuery)\n\n\t\t\t\tswitch promQuery {\n\t\t\t\tcase queryErrorRate:\n\t\t\t\t\tif tc.failErrorRateQuery {\n\t\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsendResponse(t, w, responses[callCount])\n\t\t\t\t\t}\n\t\t\t\tcase queryCallRate:\n\t\t\t\t\tif tc.failCallRateQuery {\n\t\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsendResponse(t, w, responses[callCount])\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tt.Errorf(\"Unexpected Prometheus query: %s\", promQuery)\n\t\t\t\t}\n\t\t\t\tcallCount++\n\t\t\t}))\n\n\t\t\tlogger := zap.NewNop()\n\t\t\taddress := mockPrometheus.Listener.Addr().String()\n\n\t\t\tcfg := defaultConfig\n\t\t\tcfg.ServerURL = \"http://\" + address\n\t\t\tcfg.ConnectTimeout = defaultTimeout\n\n\t\t\treader, err := NewMetricsReader(cfg, logger, tracer, nil)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdefer mockPrometheus.Close()\n\n\t\t\t_, err = reader.GetErrorRates(context.Background(), &params)\n\t\t\trequire.Error(t, err)\n\t\t\trequire.EqualError(t, err, tc.wantErr)\n\t\t})\n\t}\n}\n\nfunc TestInvalidLatencyUnit(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Error(\"Expected a panic due to invalid latency unit\")\n\t\t}\n\t}()\n\ttracer, _, closer := tracerProvider(t)\n\tdefer closer()\n\tcfg := config.Configuration{\n\t\tNormalizeDuration: true,\n\t\tLatencyUnit:       \"something invalid\",\n\t}\n\t_, _ = NewMetricsReader(cfg, zap.NewNop(), tracer, nil)\n}\n\nfunc TestWarningResponse(t *testing.T) {\n\tparams := metricstore.ErrorRateQueryParameters{\n\t\tBaseQueryParameters: buildTestBaseQueryParametersFrom(metricsTestCase{serviceNames: []string{\"foo\"}}),\n\t}\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\treader, mockPrometheus := prepareMetricsReaderAndServer(t, config.Configuration{}, \"\", []string{\"warning0\", \"warning1\"}, tracer)\n\tdefer mockPrometheus.Close()\n\n\tm, err := reader.GetErrorRates(context.Background(), &params)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, m)\n\tassert.Len(t, exp.GetSpans(), 2, \"expected an error rate query and a call rate query to be made\")\n}\n\ntype fakePromServer struct {\n\t*httptest.Server\n\tauthReceived atomic.Pointer[string]\n}\n\nfunc newFakePromServer(t *testing.T) *fakePromServer {\n\ts := &fakePromServer{}\n\ts.Server = httptest.NewServer(\n\t\thttp.HandlerFunc(\n\t\t\tfunc(_ http.ResponseWriter, r *http.Request) {\n\t\t\t\tt.Logf(\"Request to fake Prometheus server %+v\", r)\n\t\t\t\th := r.Header.Get(\"Authorization\")\n\t\t\t\ts.authReceived.Store(&h)\n\t\t\t},\n\t\t),\n\t)\n\treturn s\n}\n\nfunc (s *fakePromServer) getAuth() string {\n\treturn *s.authReceived.Load()\n}\n\nfunc TestGetRoundTripperTLSConfig(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname      string\n\t\ttlsConfig configtls.ClientConfig\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\tname: \"tls enabled with cert verification\",\n\t\t\ttlsConfig: configtls.ClientConfig{\n\t\t\t\tInsecure: false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"tls enabled insecure\",\n\t\t\ttlsConfig: configtls.ClientConfig{\n\t\t\t\tInsecure: true, // Skip verify\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid tls config\",\n\t\t\ttlsConfig: configtls.ClientConfig{\n\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\tCAFile: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tconfig := &config.Configuration{\n\t\t\t\tConnectTimeout:           9 * time.Millisecond,\n\t\t\t\tTLS:                      tc.tlsConfig,\n\t\t\t\tTokenOverrideFromContext: true,\n\t\t\t}\n\t\t\trt, err := getHTTPRoundTripper(config, nil)\n\t\t\tif tc.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\n\t\t\tserver := newFakePromServer(t)\n\t\t\tdefer server.Close()\n\n\t\t\treq, err := http.NewRequestWithContext(\n\t\t\t\tbearertoken.ContextWithBearerToken(context.Background(), \"foo\"),\n\t\t\t\thttp.MethodGet,\n\t\t\t\tserver.URL,\n\t\t\t\thttp.NoBody,\n\t\t\t)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tresp, err := rt.RoundTrip(req)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\tassert.Equal(t, \"Bearer foo\", server.getAuth())\n\t\t})\n\t}\n}\n\nfunc TestGetRoundTripperTokenFile(t *testing.T) {\n\tconst wantBearer = \"token from file\"\n\n\tfile, err := os.Create(t.TempDir() + \"token_\")\n\trequire.NoError(t, err)\n\n\t_, err = file.WriteString(wantBearer)\n\trequire.NoError(t, err)\n\trequire.NoError(t, file.Close())\n\n\trt, err := getHTTPRoundTripper(&config.Configuration{\n\t\tConnectTimeout:           time.Second,\n\t\tTokenFilePath:            file.Name(),\n\t\tTokenOverrideFromContext: false,\n\t}, nil)\n\trequire.NoError(t, err)\n\n\tserver := newFakePromServer(t)\n\tdefer server.Close()\n\n\tctx := bearertoken.ContextWithBearerToken(context.Background(), \"tokenFromRequest\")\n\treq, err := http.NewRequestWithContext(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tserver.URL,\n\t\thttp.NoBody,\n\t)\n\trequire.NoError(t, err)\n\n\tresp, err := rt.RoundTrip(req)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\tassert.Equal(t, \"Bearer \"+wantBearer, server.getAuth())\n}\n\nfunc TestGetRoundTripperTokenFromContext(t *testing.T) {\n\tfile, err := os.Create(t.TempDir() + \"token_\")\n\trequire.NoError(t, err)\n\n\t_, err = file.WriteString(\"token from file\")\n\trequire.NoError(t, err)\n\trequire.NoError(t, file.Close())\n\n\trt, err := getHTTPRoundTripper(&config.Configuration{\n\t\tConnectTimeout:           time.Second,\n\t\tTokenFilePath:            file.Name(),\n\t\tTokenOverrideFromContext: true,\n\t}, nil)\n\trequire.NoError(t, err)\n\n\tserver := newFakePromServer(t)\n\tdefer server.Close()\n\n\tctx := bearertoken.ContextWithBearerToken(context.Background(), \"tokenFromRequest\")\n\treq, err := http.NewRequestWithContext(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tserver.URL,\n\t\thttp.NoBody,\n\t)\n\trequire.NoError(t, err)\n\n\tresp, err := rt.RoundTrip(req)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\tassert.Equal(t, \"Bearer tokenFromRequest\", server.getAuth())\n}\n\nfunc TestGetRoundTripperTokenError(t *testing.T) {\n\ttokenFilePath := \"this file does not exist\"\n\n\t_, err := getHTTPRoundTripper(&config.Configuration{\n\t\tTokenFilePath: tokenFilePath,\n\t}, nil)\n\tassert.ErrorContains(t, err, \"failed to get token from file\")\n}\n\nfunc TestInvalidCertFile(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttracer, _, closer := tracerProvider(t)\n\tdefer closer()\n\treader, err := NewMetricsReader(config.Configuration{\n\t\tServerURL:      \"https://localhost:1234\",\n\t\tConnectTimeout: defaultTimeout,\n\t\tTLS: configtls.ClientConfig{\n\t\t\tConfig: configtls.Config{\n\t\t\t\tCAFile: \"foo\",\n\t\t\t},\n\t\t},\n\t}, logger, tracer, nil)\n\trequire.Error(t, err)\n\tassert.Nil(t, reader)\n}\n\nfunc TestCreatePromClientWithExtraQueryParameters(t *testing.T) {\n\textraParams := map[string]string{\n\t\t\"param1\": \"value1\",\n\t\t\"param2\": \"value2\",\n\t}\n\n\tcfg := config.Configuration{\n\t\tServerURL:        \"http://localhost:1234?param1=value0\",\n\t\tExtraQueryParams: extraParams,\n\t}\n\n\texpParams := map[string][]string{\n\t\t\"param1\": {\"value0\", \"value1\"},\n\t\t\"param2\": {\"value2\"},\n\t}\n\n\tcustomClient, err := createPromClient(cfg, nil)\n\trequire.NoError(t, err)\n\n\tu := customClient.URL(\"\", nil)\n\n\tq := u.Query()\n\n\tfor k, v := range expParams {\n\t\tsort.Strings(q[k])\n\t\trequire.Equal(t, v, q[k])\n\t}\n}\n\nfunc startMockPrometheusServer(t *testing.T, wantPromQlQuery string, wantWarnings []string) *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif len(wantWarnings) > 0 {\n\t\t\tsendResponse(t, w, \"testdata/warning_response.json\")\n\t\t\treturn\n\t\t}\n\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\tdefer r.Body.Close()\n\n\t\tu, err := url.Parse(\"http://\" + r.Host + r.RequestURI + \"?\" + string(body))\n\t\tassert.NoError(t, err)\n\n\t\tq := u.Query()\n\t\tpromQuery := q.Get(\"query\")\n\t\tassert.Equal(t, wantPromQlQuery, promQuery)\n\n\t\tmockResponsePayloadFile := \"testdata/service_datapoint_response.json\"\n\t\tif strings.Contains(promQuery, \"by (service_name,span_name\") {\n\t\t\tmockResponsePayloadFile = \"testdata/service_span_name_datapoint_response.json\"\n\t\t}\n\t\tsendResponse(t, w, mockResponsePayloadFile)\n\t}))\n}\n\nfunc sendResponse(t *testing.T, w http.ResponseWriter, responseFile string) {\n\tbytes, err := os.ReadFile(responseFile)\n\trequire.NoError(t, err)\n\n\t_, err = w.Write(bytes)\n\trequire.NoError(t, err)\n}\n\nfunc buildTestBaseQueryParametersFrom(tc metricsTestCase) metricstore.BaseQueryParameters {\n\tendTime := time.Now()\n\tlookback := time.Minute\n\tstep := time.Millisecond\n\tratePer := 10 * time.Minute\n\n\treturn metricstore.BaseQueryParameters{\n\t\tServiceNames:     tc.serviceNames,\n\t\tGroupByOperation: tc.groupByOperation,\n\t\tEndTime:          &endTime,\n\t\tLookback:         &lookback,\n\t\tStep:             &step,\n\t\tRatePer:          &ratePer,\n\t\tSpanKinds:        tc.spanKinds,\n\t}\n}\n\nfunc prepareMetricsReaderAndServer(t *testing.T, cfg config.Configuration, wantPromQlQuery string, wantWarnings []string, tracer trace.TracerProvider) (metricstore.Reader, *httptest.Server) {\n\tmockPrometheus := startMockPrometheusServer(t, wantPromQlQuery, wantWarnings)\n\n\tlogger := zap.NewNop()\n\taddress := mockPrometheus.Listener.Addr().String()\n\n\tcfg.ServerURL = \"http://\" + address\n\tcfg.ConnectTimeout = defaultTimeout\n\n\treader, err := NewMetricsReader(cfg, logger, tracer, nil)\n\trequire.NoError(t, err)\n\n\treturn reader, mockPrometheus\n}\n\nfunc assertMetrics(t *testing.T, gotMetrics *metrics.MetricFamily, wantLabels map[string]string, wantName, wantDescription string) {\n\tassert.Len(t, gotMetrics.Metrics, 1)\n\tassert.Equal(t, wantName, gotMetrics.Name)\n\tassert.Equal(t, wantDescription, gotMetrics.Help)\n\tmps := gotMetrics.Metrics[0].MetricPoints\n\tassert.Len(t, mps, 1)\n\n\t// logging for expected and actual labels\n\tt.Logf(\"Expected labels: %v\\n\", wantLabels)\n\tt.Logf(\"Actual labels: %v\\n\", gotMetrics.Metrics[0].Labels)\n\n\t// There is no guaranteed order of labels, so we need to take the approach of using a map of expected values.\n\tlabels := gotMetrics.Metrics[0].Labels\n\tassert.Len(t, labels, len(wantLabels))\n\tfor _, l := range labels {\n\t\tassert.Contains(t, wantLabels, l.Name)\n\t\tassert.Equal(t, wantLabels[l.Name], l.Value)\n\t\tdelete(wantLabels, l.Name)\n\t}\n\tassert.Empty(t, wantLabels)\n\n\t// Additional logging to show that all expected labels were found and matched\n\tt.Logf(\"Remaining expected labels after matching: %v\\n\", wantLabels)\n\tt.Log(\"\\n\")\n\n\tassert.Equal(t, int64(1620351786), mps[0].Timestamp.GetSeconds())\n\n\tactualVal := mps[0].Value.(*metrics.MetricPoint_GaugeValue).GaugeValue.Value.(*metrics.GaugeValue_DoubleValue).DoubleValue\n\tassert.InDelta(t, float64(9223372036854), actualVal, 0.01)\n}\n\nfunc TestNewMetricsReaderWithHTTPAuth(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\thttpAuth *mockHTTPAuthenticator\n\t}{\n\t\t{\n\t\t\tname:     \"with HTTP authenticator\",\n\t\t\thttpAuth: &mockHTTPAuthenticator{},\n\t\t},\n\t\t{\n\t\t\tname:     \"without HTTP authenticator\",\n\t\t\thttpAuth: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tauthHeaderReceived := \"\"\n\t\t\tmockPrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tauthHeaderReceived = r.Header.Get(\"Authorization\")\n\t\t\t\tsendResponse(t, w, \"testdata/service_datapoint_response.json\")\n\t\t\t}))\n\t\t\tdefer mockPrometheus.Close()\n\n\t\t\tlogger := zap.NewNop()\n\t\t\ttracer, _, closer := tracerProvider(t)\n\t\t\tdefer closer()\n\n\t\t\tcfg := config.Configuration{\n\t\t\t\tServerURL:      mockPrometheus.URL,\n\t\t\t\tConnectTimeout: defaultTimeout,\n\t\t\t}\n\n\t\t\treader, err := NewMetricsReader(cfg, logger, tracer, tt.httpAuth)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, reader)\n\n\t\t\tendTime := time.Now()\n\t\t\tlookback := time.Minute\n\t\t\tstep := time.Millisecond\n\t\t\tratePer := 10 * time.Minute\n\n\t\t\tparams := metricstore.CallRateQueryParameters{\n\t\t\t\tBaseQueryParameters: metricstore.BaseQueryParameters{\n\t\t\t\t\tServiceNames: []string{\"emailservice\"},\n\t\t\t\t\tEndTime:      &endTime,\n\t\t\t\t\tLookback:     &lookback,\n\t\t\t\t\tStep:         &step,\n\t\t\t\t\tRatePer:      &ratePer,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t_, err = reader.GetCallRates(context.Background(), &params)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif tt.httpAuth != nil {\n\t\t\t\tassert.Equal(t, \"Bearer sigv4-token\", authHeaderReceived)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockHTTPAuthenticator struct{}\n\nfunc (*mockHTTPAuthenticator) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {\n\treturn &mockAuthRoundTripper{base: base}, nil\n}\n\ntype mockAuthRoundTripper struct {\n\tbase http.RoundTripper\n}\n\nfunc (m *mockAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Set(\"Authorization\", \"Bearer sigv4-token\")\n\tif m.base != nil {\n\t\treturn m.base.RoundTrip(req)\n\t}\n\treturn &http.Response{\n\t\tStatusCode: http.StatusOK,\n\t\tBody:       http.NoBody,\n\t}, nil\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/testdata/empty_response.json",
    "content": "{\n  \"status\": \"success\",\n  \"data\": {\n    \"resultType\": \"matrix\",\n    \"result\": []\n  }\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/testdata/service_datapoint_response.json",
    "content": "{\n  \"status\": \"success\",\n  \"data\": {\n    \"resultType\": \"matrix\",\n    \"result\": [\n      {\n        \"metric\": {\n          \"service_name\": \"emailservice\"\n        },\n        \"values\": [\n          [\n            1620351786,\n            \"9223372036854\"\n          ]\n        ]\n      }\n    ]\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/testdata/service_span_name_datapoint_response.json",
    "content": "{\n  \"status\": \"success\",\n  \"data\": {\n    \"resultType\": \"matrix\",\n    \"result\": [\n      {\n        \"metric\": {\n          \"span_name\": \"/OrderResult\",\n          \"service_name\": \"emailservice\"\n        },\n        \"values\": [\n          [\n            1620351786,\n            \"9223372036854\"\n          ]\n        ]\n      }\n    ]\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/prometheus/metricstore/testdata/warning_response.json",
    "content": "{\n  \"status\": \"warning\",\n  \"warnings\": [\"warning0\", \"warning1\"],\n  \"data\": {\n    \"resultType\": \"matrix\",\n    \"result\": []\n  }\n}"
  },
  {
    "path": "internal/storage/metricstore/prometheus/options.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/viper\"\n\n\tconfig \"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n\t\"github.com/jaegertracing/jaeger/internal/config/tlscfg\"\n)\n\nconst (\n\tprefix = \"prometheus\"\n\n\tsuffixServerURL           = \".server-url\"\n\tsuffixConnectTimeout      = \".connect-timeout\"\n\tsuffixTokenFilePath       = \".token-file\"\n\tsuffixOverrideFromContext = \".token-override-from-context\"\n\n\tsuffixMetricNamespace   = \".query.namespace\"\n\tsuffixLatencyUnit       = \".query.duration-unit\"\n\tsuffixNormalizeCalls    = \".query.normalize-calls\"\n\tsuffixNormalizeDuration = \".query.normalize-duration\"\n\tsuffixExtraQueryParams  = \".query.extra-query-params\"\n\n\tdefaultServerURL      = \"http://localhost:9090\"\n\tdefaultConnectTimeout = 30 * time.Second\n\tdefaultTokenFilePath  = \"\"\n\n\t// the default configuration here matches the default namespace in the span metrics connector\n\tdefaultMetricNamespace   = \"traces_span_metrics\"\n\tdefaultLatencyUnit       = \"ms\"\n\tdefaultNormalizeCalls    = false\n\tdefaultNormalizeDuration = false\n)\n\n// Options stores the configuration entries for this storage.\ntype Options struct {\n\tconfig.Configuration `mapstructure:\",squash\"`\n}\n\nvar tlsFlagsCfg = tlscfg.ClientFlagsConfig{Prefix: prefix}\n\nfunc DefaultConfig() config.Configuration {\n\treturn config.Configuration{\n\t\tServerURL:      defaultServerURL,\n\t\tConnectTimeout: defaultConnectTimeout,\n\n\t\tMetricNamespace:   defaultMetricNamespace,\n\t\tLatencyUnit:       defaultLatencyUnit,\n\t\tNormalizeCalls:    defaultNormalizeCalls,\n\t\tNormalizeDuration: defaultNormalizeCalls,\n\t}\n}\n\n// NewOptions creates a new Options struct.\nfunc NewOptions() *Options {\n\treturn &Options{\n\t\tConfiguration: DefaultConfig(),\n\t}\n}\n\n// AddFlags from this storage to the CLI.\nfunc (*Options) AddFlags(flagSet *flag.FlagSet) {\n\tflagSet.String(prefix+suffixServerURL, defaultServerURL,\n\t\t\"The Prometheus server's URL, must include the protocol scheme e.g. http://localhost:9090\")\n\tflagSet.Duration(prefix+suffixConnectTimeout, defaultConnectTimeout,\n\t\t\"The period to wait for a connection to Prometheus when executing queries.\")\n\tflagSet.String(prefix+suffixTokenFilePath, defaultTokenFilePath,\n\t\t\"The path to a file containing the bearer token which will be included when executing queries against the Prometheus API.\")\n\tflagSet.Bool(prefix+suffixOverrideFromContext, true,\n\t\t\"Whether the bearer token should be overridden from context (incoming request)\")\n\tflagSet.String(prefix+suffixMetricNamespace, defaultMetricNamespace,\n\t\t`The metric namespace that is prefixed to the metric name. A '.' separator will be added between `+\n\t\t\t`the namespace and the metric name.`)\n\tflagSet.String(prefix+suffixLatencyUnit, defaultLatencyUnit,\n\t\t`The units used for the \"latency\" histogram. It can be either \"ms\" or \"s\" and should be consistent with the `+\n\t\t\t`histogram unit value set in the spanmetrics connector (see: `+\n\t\t\t`https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/spanmetricsconnector#configurations). `+\n\t\t\t`This also helps jaeger-query determine the metric name when querying for \"latency\" metrics.`)\n\tflagSet.Bool(prefix+suffixNormalizeCalls, defaultNormalizeCalls,\n\t\t`Whether to normalize the \"calls\" metric name according to `+\n\t\t\t`https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/translator/prometheus/README.md. `+\n\t\t\t`For example: `+\n\t\t\t`\"calls\" (not normalized) -> \"calls_total\" (normalized), `)\n\tflagSet.Bool(prefix+suffixNormalizeDuration, defaultNormalizeDuration,\n\t\t`Whether to normalize the \"duration\" metric name according to `+\n\t\t\t`https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/translator/prometheus/README.md. `+\n\t\t\t`For example: `+\n\t\t\t`\"duration_bucket\" (not normalized) -> \"duration_milliseconds_bucket (normalized)\"`)\n\tflagSet.String(prefix+suffixExtraQueryParams, \"\",\n\t\t\"A comma separated list of param=value pairs of query parameters, which are appended on all API requests to the Prometheus API. \"+\n\t\t\t\"Example: param1=value2,param2=value2\")\n\n\ttlsFlagsCfg.AddFlags(flagSet)\n}\n\n// InitFromViper initializes the options struct with values from Viper.\nfunc (opt *Options) InitFromViper(v *viper.Viper) error {\n\topt.ServerURL = stripWhiteSpace(v.GetString(prefix + suffixServerURL))\n\topt.ConnectTimeout = v.GetDuration(prefix + suffixConnectTimeout)\n\topt.TokenFilePath = v.GetString(prefix + suffixTokenFilePath)\n\n\topt.MetricNamespace = v.GetString(prefix + suffixMetricNamespace)\n\topt.LatencyUnit = v.GetString(prefix + suffixLatencyUnit)\n\topt.NormalizeCalls = v.GetBool(prefix + suffixNormalizeCalls)\n\topt.NormalizeDuration = v.GetBool(prefix + suffixNormalizeDuration)\n\topt.TokenOverrideFromContext = v.GetBool(prefix + suffixOverrideFromContext)\n\n\tvar err error\n\topt.ExtraQueryParams, err = parseKV(stripWhiteSpace(v.GetString(prefix + suffixExtraQueryParams)))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse extra query params: %w\", err)\n\t}\n\n\tisValidUnit := map[string]bool{\"ms\": true, \"s\": true}\n\tif _, ok := isValidUnit[opt.LatencyUnit]; !ok {\n\t\treturn fmt.Errorf(`duration-unit must be one of \"ms\" or \"s\", not %q`, opt.LatencyUnit)\n\t}\n\n\ttlsCfg, err := tlsFlagsCfg.InitFromViper(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to process Prometheus TLS options: %w\", err)\n\t}\n\topt.TLS = tlsCfg\n\treturn nil\n}\n\n// stripWhiteSpace removes all whitespace characters from a string.\nfunc stripWhiteSpace(str string) string {\n\treturn strings.ReplaceAll(str, \" \", \"\")\n}\n\n// parseKV parses a comma separated list of key=value pairs into a map\nfunc parseKV(input string) (map[string]string, error) {\n\tif input == \"\" {\n\t\treturn map[string]string{}, nil\n\t}\n\n\tret := map[string]string{}\n\tfor entry := range strings.SplitSeq(input, \",\") {\n\t\tkv := strings.Split(entry, \"=\")\n\t\tif len(kv) != 2 {\n\t\t\treturn map[string]string{}, fmt.Errorf(\"failed to parse '%s'. Expected format: 'param1=value1,param2=value2'\", input)\n\t\t}\n\t\tret[kv[0]] = kv[1]\n\t}\n\treturn ret, nil\n}\n"
  },
  {
    "path": "internal/storage/metricstore/prometheus/options_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage prometheus\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\tconfig \"github.com/jaegertracing/jaeger/internal/config/promcfg\"\n)\n\nfunc TestCLI(t *testing.T) {\n\topts := Options{\n\t\tConfiguration: config.Configuration{\n\t\t\tExtraQueryParams: map[string]string{\"key1\": \"value1\"},\n\t\t},\n\t}\n\n\tassert.Equal(t, map[string]string{\"key1\": \"value1\"}, opts.ExtraQueryParams)\n}\n\nfunc TestCLIError(t *testing.T) {\n\t_, err := parseKV(\"key1\")\n\tassert.ErrorContains(t, err, \"failed to parse 'key1'. Expected format: 'param1=value1,param2=value2'\")\n}\n\nfunc TestParseKV(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected map[string]string\n\t\terr      error\n\t}{\n\t\t{\n\t\t\tinput:    \"\",\n\t\t\texpected: map[string]string{},\n\t\t\terr:      nil,\n\t\t},\n\t\t{\n\t\t\tinput:    \"key1=value1\",\n\t\t\texpected: map[string]string{\"key1\": \"value1\"},\n\t\t\terr:      nil,\n\t\t},\n\t\t{\n\t\t\tinput:    \"key1=value1,key2=value2\",\n\t\t\texpected: map[string]string{\"key1\": \"value1\", \"key2\": \"value2\"},\n\t\t\terr:      nil,\n\t\t},\n\t\t{\n\t\t\tinput:    \"key1=value1,key2\",\n\t\t\texpected: map[string]string{},\n\t\t\terr:      errors.New(\"failed to parse 'key1=value1,key2'. Expected format: 'param1=value1,param2=value2'\"),\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tkv, err := parseKV(test.input)\n\t\t\tassert.Equal(t, test.expected, kv)\n\t\t\tassert.Equal(t, test.err, err)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/api/README.md",
    "content": "The collection of different storage interfaces that are shared by two or more components\n\nIf a storage is used by only one component, its interface should be defined in the component package, and implementations under `./plugin/storage/{db_name}/{store_type}/...`.\n\n"
  },
  {
    "path": "internal/storage/v1/api/dependencystore/empty_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/dependencystore/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// Writer stores service dependencies into storage.\ntype Writer interface {\n\tWriteDependencies(ts time.Time, dependencies []model.DependencyLink) error\n}\n\n// Reader can load service dependencies from storage.\ntype Reader interface {\n\tGetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/dependencystore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewReader creates a new instance of Reader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Reader {\n\tmock := &Reader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Reader is an autogenerated mock type for the Reader type\ntype Reader struct {\n\tmock.Mock\n}\n\ntype Reader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Reader) EXPECT() *Reader_Expecter {\n\treturn &Reader_Expecter{mock: &_m.Mock}\n}\n\n// GetDependencies provides a mock function for the type Reader\nfunc (_mock *Reader) GetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error) {\n\tret := _mock.Called(ctx, endTs, lookback)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetDependencies\")\n\t}\n\n\tvar r0 []model.DependencyLink\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, time.Time, time.Duration) ([]model.DependencyLink, error)); ok {\n\t\treturn returnFunc(ctx, endTs, lookback)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, time.Time, time.Duration) []model.DependencyLink); ok {\n\t\tr0 = returnFunc(ctx, endTs, lookback)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]model.DependencyLink)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, time.Time, time.Duration) error); ok {\n\t\tr1 = returnFunc(ctx, endTs, lookback)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDependencies'\ntype Reader_GetDependencies_Call struct {\n\t*mock.Call\n}\n\n// GetDependencies is a helper method to define mock.On call\n//   - ctx context.Context\n//   - endTs time.Time\n//   - lookback time.Duration\nfunc (_e *Reader_Expecter) GetDependencies(ctx interface{}, endTs interface{}, lookback interface{}) *Reader_GetDependencies_Call {\n\treturn &Reader_GetDependencies_Call{Call: _e.mock.On(\"GetDependencies\", ctx, endTs, lookback)}\n}\n\nfunc (_c *Reader_GetDependencies_Call) Run(run func(ctx context.Context, endTs time.Time, lookback time.Duration)) *Reader_GetDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 time.Time\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(time.Time)\n\t\t}\n\t\tvar arg2 time.Duration\n\t\tif args[2] != nil {\n\t\t\targ2 = args[2].(time.Duration)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetDependencies_Call) Return(dependencyLinks []model.DependencyLink, err error) *Reader_GetDependencies_Call {\n\t_c.Call.Return(dependencyLinks, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetDependencies_Call) RunAndReturn(run func(ctx context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error)) *Reader_GetDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/api/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package storage is the collection of different storage interfaces that are shared by two or more components.\n//\n// If a storage is used by only one component, its interface should be defined in the component package, and implementations under ./plugin/storage/{db_name}/{store_type}/....\npackage storage\n"
  },
  {
    "path": "internal/storage/v1/api/empty_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storage\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/metricstore/empty_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/metricstore/interface.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n)\n\n// Reader can load aggregated trace metrics from storage.\ntype Reader interface {\n\t// GetLatencies gets the latency metrics for a specific quantile (e.g. 0.99) and list of services\n\t// grouped by service and optionally grouped by operation.\n\tGetLatencies(ctx context.Context, params *LatenciesQueryParameters) (*metrics.MetricFamily, error)\n\t// GetCallRates gets the call rate metrics for a given list of services grouped by service\n\t// and optionally grouped by operation.\n\tGetCallRates(ctx context.Context, params *CallRateQueryParameters) (*metrics.MetricFamily, error)\n\t// GetErrorRates gets the error rate metrics for a given list of services grouped by service\n\t// and optionally grouped by operation.\n\tGetErrorRates(ctx context.Context, params *ErrorRateQueryParameters) (*metrics.MetricFamily, error)\n\t// GetMinStepDuration gets the min time resolution supported by the backing metrics store,\n\t// e.g. 10s means the backend can only return data points that are at least 10s apart, not closer.\n\tGetMinStepDuration(ctx context.Context, params *MinStepDurationQueryParameters) (time.Duration, error)\n}\n\n// BaseQueryParameters contains the common set of parameters used by all metrics queries:\n// latency, call rate or error rate.\ntype BaseQueryParameters struct {\n\t// ServiceNames are the service names to fetch metrics from. The results will be grouped by service_name.\n\tServiceNames []string\n\t// GroupByOperation determines if the metrics returned should be grouped by operation.\n\tGroupByOperation bool\n\t// EndTime is the ending time of the time series query range.\n\tEndTime *time.Time\n\t// Lookback is the duration from the end_time to look back on for metrics data points.\n\t// For example, if set to 1h, the query would span from end_time-1h to end_time.\n\tLookback *time.Duration\n\t// Step size is the duration between data points of the query results.\n\t// For example, if set to 5s, the results would produce a data point every 5 seconds from the (EndTime - Lookback) to EndTime.\n\tStep *time.Duration\n\t// RatePer is the duration in which the per-second rate of change is calculated for a cumulative counter metric.\n\tRatePer *time.Duration\n\t// SpanKinds is the list of span kinds to include (logical OR) in the resulting metrics aggregation.\n\tSpanKinds []string\n}\n\n// LatenciesQueryParameters contains the parameters required for latency metrics queries.\ntype LatenciesQueryParameters struct {\n\tBaseQueryParameters\n\t// Quantile is the quantile to compute from latency histogram metrics.\n\t// Valid range: 0 - 1 (inclusive).\n\t//\n\t// e.g. 0.99 will return the 99th percentile or P99 which is the worst latency\n\t// observed from 99% of all spans for the given service (and operation).\n\tQuantile float64\n}\n\n// CallRateQueryParameters contains the parameters required for call rate metrics queries.\ntype CallRateQueryParameters struct {\n\tBaseQueryParameters\n}\n\n// ErrorRateQueryParameters contains the parameters required for error rate metrics queries.\ntype ErrorRateQueryParameters struct {\n\tBaseQueryParameters\n}\n\n// MinStepDurationQueryParameters contains the parameters required for fetching the minimum step duration.\ntype MinStepDurationQueryParameters struct{}\n"
  },
  {
    "path": "internal/storage/v1/api/metricstore/metricstoremetrics/decorator.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstoremetrics\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tprotometrics \"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n)\n\n// ReadMetricsDecorator wraps a metricstore.Reader and collects metrics around each read operation.\ntype ReadMetricsDecorator struct {\n\treader                    metricstore.Reader\n\tgetLatenciesMetrics       *queryMetrics\n\tgetCallRatesMetrics       *queryMetrics\n\tgetErrorRatesMetrics      *queryMetrics\n\tgetMinStepDurationMetrics *queryMetrics\n}\n\ntype queryMetrics struct {\n\tErrors     metrics.Counter `metric:\"requests\" tags:\"result=err\"`\n\tSuccesses  metrics.Counter `metric:\"requests\" tags:\"result=ok\"`\n\tErrLatency metrics.Timer   `metric:\"latency\" tags:\"result=err\"`\n\tOKLatency  metrics.Timer   `metric:\"latency\" tags:\"result=ok\"`\n}\n\nfunc (q *queryMetrics) emit(err error, latency time.Duration) {\n\tif err != nil {\n\t\tq.Errors.Inc(1)\n\t\tq.ErrLatency.Record(latency)\n\t} else {\n\t\tq.Successes.Inc(1)\n\t\tq.OKLatency.Record(latency)\n\t}\n}\n\n// NewReadMetricsDecorator returns a new ReadMetricsDecorator.\nfunc NewReaderDecorator(reader metricstore.Reader, metricsFactory metrics.Factory) *ReadMetricsDecorator {\n\treturn &ReadMetricsDecorator{\n\t\treader:                    reader,\n\t\tgetLatenciesMetrics:       buildQueryMetrics(\"get_latencies\", metricsFactory),\n\t\tgetCallRatesMetrics:       buildQueryMetrics(\"get_call_rates\", metricsFactory),\n\t\tgetErrorRatesMetrics:      buildQueryMetrics(\"get_error_rates\", metricsFactory),\n\t\tgetMinStepDurationMetrics: buildQueryMetrics(\"get_min_step_duration\", metricsFactory),\n\t}\n}\n\nfunc buildQueryMetrics(operation string, metricsFactory metrics.Factory) *queryMetrics {\n\tqMetrics := &queryMetrics{}\n\tscoped := metricsFactory.Namespace(metrics.NSOptions{Name: \"\", Tags: map[string]string{\"operation\": operation}})\n\tmetrics.Init(qMetrics, scoped, nil)\n\treturn qMetrics\n}\n\n// GetLatencies implements metricstore.Reader#GetLatencies\nfunc (m *ReadMetricsDecorator) GetLatencies(ctx context.Context, params *metricstore.LatenciesQueryParameters) (*protometrics.MetricFamily, error) {\n\tstart := time.Now()\n\tretMe, err := m.reader.GetLatencies(ctx, params)\n\tm.getLatenciesMetrics.emit(err, time.Since(start))\n\treturn retMe, err\n}\n\n// GetCallRates implements metricstore.Reader#GetCallRates\nfunc (m *ReadMetricsDecorator) GetCallRates(ctx context.Context, params *metricstore.CallRateQueryParameters) (*protometrics.MetricFamily, error) {\n\tstart := time.Now()\n\tretMe, err := m.reader.GetCallRates(ctx, params)\n\tm.getCallRatesMetrics.emit(err, time.Since(start))\n\treturn retMe, err\n}\n\n// GetErrorRates implements metricstore.Reader#GetErrorRates\nfunc (m *ReadMetricsDecorator) GetErrorRates(ctx context.Context, params *metricstore.ErrorRateQueryParameters) (*protometrics.MetricFamily, error) {\n\tstart := time.Now()\n\tretMe, err := m.reader.GetErrorRates(ctx, params)\n\tm.getErrorRatesMetrics.emit(err, time.Since(start))\n\treturn retMe, err\n}\n\n// GetMinStepDuration implements metricstore.Reader#GetMinStepDuration\nfunc (m *ReadMetricsDecorator) GetMinStepDuration(ctx context.Context, params *metricstore.MinStepDurationQueryParameters) (time.Duration, error) {\n\tstart := time.Now()\n\tretMe, err := m.reader.GetMinStepDuration(ctx, params)\n\tm.getMinStepDurationMetrics.emit(err, time.Since(start))\n\treturn retMe, err\n}\n"
  },
  {
    "path": "internal/storage/v1/api/metricstore/metricstoremetrics/decorator_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage metricstoremetrics_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\tprotometrics \"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore/metricstoremetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestSuccessfulUnderlyingCalls(t *testing.T) {\n\tmf := metricstest.NewFactory(0)\n\n\tmockReader := mocks.Reader{}\n\tmrs := metricstoremetrics.NewReaderDecorator(&mockReader, mf)\n\tglParams := &metricstore.LatenciesQueryParameters{}\n\tmockReader.On(\"GetLatencies\", context.Background(), glParams).\n\t\tReturn(&protometrics.MetricFamily{}, nil)\n\tmrs.GetLatencies(context.Background(), glParams)\n\n\tgcrParams := &metricstore.CallRateQueryParameters{}\n\tmockReader.On(\"GetCallRates\", context.Background(), gcrParams).\n\t\tReturn(&protometrics.MetricFamily{}, nil)\n\tmrs.GetCallRates(context.Background(), gcrParams)\n\n\tgerParams := &metricstore.ErrorRateQueryParameters{}\n\tmockReader.On(\"GetErrorRates\", context.Background(), gerParams).\n\t\tReturn(&protometrics.MetricFamily{}, nil)\n\tmrs.GetErrorRates(context.Background(), gerParams)\n\n\tmsdParams := &metricstore.MinStepDurationQueryParameters{}\n\tmockReader.On(\"GetMinStepDuration\", context.Background(), msdParams).\n\t\tReturn(time.Second, nil)\n\tmrs.GetMinStepDuration(context.Background(), msdParams)\n\n\tcounters, gauges := mf.Snapshot()\n\twantCounts := map[string]int64{\n\t\t\"requests|operation=get_latencies|result=ok\":          1,\n\t\t\"requests|operation=get_latencies|result=err\":         0,\n\t\t\"requests|operation=get_call_rates|result=ok\":         1,\n\t\t\"requests|operation=get_call_rates|result=err\":        0,\n\t\t\"requests|operation=get_error_rates|result=ok\":        1,\n\t\t\"requests|operation=get_error_rates|result=err\":       0,\n\t\t\"requests|operation=get_min_step_duration|result=ok\":  1,\n\t\t\"requests|operation=get_min_step_duration|result=err\": 0,\n\t}\n\n\t// This is not exhaustive.\n\twantExistingKeys := []string{\n\t\t\"latency|operation=get_latencies|result=ok.P50\",\n\t\t\"latency|operation=get_error_rates|result=ok.P50\",\n\t}\n\n\t// This is not exhaustive.\n\twantNonExistentKeys := []string{\n\t\t\"latency|operation=get_latencies|result=err.P50\",\n\t}\n\n\tcheckExpectedExistingAndNonExistentCounters(t, counters, wantCounts, gauges, wantExistingKeys, wantNonExistentKeys)\n}\n\nfunc checkExpectedExistingAndNonExistentCounters(t *testing.T,\n\tactualCounters,\n\texpectedCounters,\n\tactualGauges map[string]int64,\n\texistingKeys,\n\tnonExistentKeys []string,\n) {\n\tfor k, v := range expectedCounters {\n\t\tassert.Equal(t, v, actualCounters[k], k)\n\t}\n\n\tfor _, k := range existingKeys {\n\t\t_, ok := actualGauges[k]\n\t\tassert.True(t, ok)\n\t}\n\n\tfor _, k := range nonExistentKeys {\n\t\t_, ok := actualGauges[k]\n\t\tassert.False(t, ok)\n\t}\n}\n\nfunc TestFailingUnderlyingCalls(t *testing.T) {\n\tmf := metricstest.NewFactory(0)\n\n\tmockReader := mocks.Reader{}\n\tmrs := metricstoremetrics.NewReaderDecorator(&mockReader, mf)\n\tglParams := &metricstore.LatenciesQueryParameters{}\n\tmockReader.On(\"GetLatencies\", context.Background(), glParams).\n\t\tReturn(&protometrics.MetricFamily{}, errors.New(\"failure\"))\n\tmrs.GetLatencies(context.Background(), glParams)\n\n\tgcrParams := &metricstore.CallRateQueryParameters{}\n\tmockReader.On(\"GetCallRates\", context.Background(), gcrParams).\n\t\tReturn(&protometrics.MetricFamily{}, errors.New(\"failure\"))\n\tmrs.GetCallRates(context.Background(), gcrParams)\n\n\tgerParams := &metricstore.ErrorRateQueryParameters{}\n\tmockReader.On(\"GetErrorRates\", context.Background(), gerParams).\n\t\tReturn(&protometrics.MetricFamily{}, errors.New(\"failure\"))\n\tmrs.GetErrorRates(context.Background(), gerParams)\n\n\tmsdParams := &metricstore.MinStepDurationQueryParameters{}\n\tmockReader.On(\"GetMinStepDuration\", context.Background(), msdParams).\n\t\tReturn(time.Second, errors.New(\"failure\"))\n\tmrs.GetMinStepDuration(context.Background(), msdParams)\n\n\tcounters, gauges := mf.Snapshot()\n\twantCounts := map[string]int64{\n\t\t\"requests|operation=get_latencies|result=ok\":          0,\n\t\t\"requests|operation=get_latencies|result=err\":         1,\n\t\t\"requests|operation=get_call_rates|result=ok\":         0,\n\t\t\"requests|operation=get_call_rates|result=err\":        1,\n\t\t\"requests|operation=get_error_rates|result=ok\":        0,\n\t\t\"requests|operation=get_error_rates|result=err\":       1,\n\t\t\"requests|operation=get_min_step_duration|result=ok\":  0,\n\t\t\"requests|operation=get_min_step_duration|result=err\": 1,\n\t}\n\n\t// This is not exhaustive.\n\twantExistingKeys := []string{\n\t\t\"latency|operation=get_latencies|result=err.P50\",\n\t}\n\n\t// This is not exhaustive.\n\twantNonExistentKeys := []string{\n\t\t\"latency|operation=get_latencies|result=ok.P50\",\n\t\t\"latency|operation=get_error_rates|result=ok.P50\",\n\t}\n\n\tcheckExpectedExistingAndNonExistentCounters(t, counters, wantCounts, gauges, wantExistingKeys, wantNonExistentKeys)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/metricstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewReader creates a new instance of Reader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Reader {\n\tmock := &Reader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Reader is an autogenerated mock type for the Reader type\ntype Reader struct {\n\tmock.Mock\n}\n\ntype Reader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Reader) EXPECT() *Reader_Expecter {\n\treturn &Reader_Expecter{mock: &_m.Mock}\n}\n\n// GetCallRates provides a mock function for the type Reader\nfunc (_mock *Reader) GetCallRates(ctx context.Context, params *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error) {\n\tret := _mock.Called(ctx, params)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetCallRates\")\n\t}\n\n\tvar r0 *metrics.MetricFamily\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error)); ok {\n\t\treturn returnFunc(ctx, params)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.CallRateQueryParameters) *metrics.MetricFamily); ok {\n\t\tr0 = returnFunc(ctx, params)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*metrics.MetricFamily)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *metricstore.CallRateQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, params)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetCallRates_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCallRates'\ntype Reader_GetCallRates_Call struct {\n\t*mock.Call\n}\n\n// GetCallRates is a helper method to define mock.On call\n//   - ctx context.Context\n//   - params *metricstore.CallRateQueryParameters\nfunc (_e *Reader_Expecter) GetCallRates(ctx interface{}, params interface{}) *Reader_GetCallRates_Call {\n\treturn &Reader_GetCallRates_Call{Call: _e.mock.On(\"GetCallRates\", ctx, params)}\n}\n\nfunc (_c *Reader_GetCallRates_Call) Run(run func(ctx context.Context, params *metricstore.CallRateQueryParameters)) *Reader_GetCallRates_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *metricstore.CallRateQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*metricstore.CallRateQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetCallRates_Call) Return(metricFamily *metrics.MetricFamily, err error) *Reader_GetCallRates_Call {\n\t_c.Call.Return(metricFamily, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetCallRates_Call) RunAndReturn(run func(ctx context.Context, params *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error)) *Reader_GetCallRates_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetErrorRates provides a mock function for the type Reader\nfunc (_mock *Reader) GetErrorRates(ctx context.Context, params *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error) {\n\tret := _mock.Called(ctx, params)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetErrorRates\")\n\t}\n\n\tvar r0 *metrics.MetricFamily\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error)); ok {\n\t\treturn returnFunc(ctx, params)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.ErrorRateQueryParameters) *metrics.MetricFamily); ok {\n\t\tr0 = returnFunc(ctx, params)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*metrics.MetricFamily)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *metricstore.ErrorRateQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, params)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetErrorRates_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetErrorRates'\ntype Reader_GetErrorRates_Call struct {\n\t*mock.Call\n}\n\n// GetErrorRates is a helper method to define mock.On call\n//   - ctx context.Context\n//   - params *metricstore.ErrorRateQueryParameters\nfunc (_e *Reader_Expecter) GetErrorRates(ctx interface{}, params interface{}) *Reader_GetErrorRates_Call {\n\treturn &Reader_GetErrorRates_Call{Call: _e.mock.On(\"GetErrorRates\", ctx, params)}\n}\n\nfunc (_c *Reader_GetErrorRates_Call) Run(run func(ctx context.Context, params *metricstore.ErrorRateQueryParameters)) *Reader_GetErrorRates_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *metricstore.ErrorRateQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*metricstore.ErrorRateQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetErrorRates_Call) Return(metricFamily *metrics.MetricFamily, err error) *Reader_GetErrorRates_Call {\n\t_c.Call.Return(metricFamily, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetErrorRates_Call) RunAndReturn(run func(ctx context.Context, params *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error)) *Reader_GetErrorRates_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetLatencies provides a mock function for the type Reader\nfunc (_mock *Reader) GetLatencies(ctx context.Context, params *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error) {\n\tret := _mock.Called(ctx, params)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetLatencies\")\n\t}\n\n\tvar r0 *metrics.MetricFamily\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error)); ok {\n\t\treturn returnFunc(ctx, params)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.LatenciesQueryParameters) *metrics.MetricFamily); ok {\n\t\tr0 = returnFunc(ctx, params)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*metrics.MetricFamily)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *metricstore.LatenciesQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, params)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetLatencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatencies'\ntype Reader_GetLatencies_Call struct {\n\t*mock.Call\n}\n\n// GetLatencies is a helper method to define mock.On call\n//   - ctx context.Context\n//   - params *metricstore.LatenciesQueryParameters\nfunc (_e *Reader_Expecter) GetLatencies(ctx interface{}, params interface{}) *Reader_GetLatencies_Call {\n\treturn &Reader_GetLatencies_Call{Call: _e.mock.On(\"GetLatencies\", ctx, params)}\n}\n\nfunc (_c *Reader_GetLatencies_Call) Run(run func(ctx context.Context, params *metricstore.LatenciesQueryParameters)) *Reader_GetLatencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *metricstore.LatenciesQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*metricstore.LatenciesQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetLatencies_Call) Return(metricFamily *metrics.MetricFamily, err error) *Reader_GetLatencies_Call {\n\t_c.Call.Return(metricFamily, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetLatencies_Call) RunAndReturn(run func(ctx context.Context, params *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error)) *Reader_GetLatencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetMinStepDuration provides a mock function for the type Reader\nfunc (_mock *Reader) GetMinStepDuration(ctx context.Context, params *metricstore.MinStepDurationQueryParameters) (time.Duration, error) {\n\tret := _mock.Called(ctx, params)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetMinStepDuration\")\n\t}\n\n\tvar r0 time.Duration\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.MinStepDurationQueryParameters) (time.Duration, error)); ok {\n\t\treturn returnFunc(ctx, params)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *metricstore.MinStepDurationQueryParameters) time.Duration); ok {\n\t\tr0 = returnFunc(ctx, params)\n\t} else {\n\t\tr0 = ret.Get(0).(time.Duration)\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *metricstore.MinStepDurationQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, params)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetMinStepDuration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMinStepDuration'\ntype Reader_GetMinStepDuration_Call struct {\n\t*mock.Call\n}\n\n// GetMinStepDuration is a helper method to define mock.On call\n//   - ctx context.Context\n//   - params *metricstore.MinStepDurationQueryParameters\nfunc (_e *Reader_Expecter) GetMinStepDuration(ctx interface{}, params interface{}) *Reader_GetMinStepDuration_Call {\n\treturn &Reader_GetMinStepDuration_Call{Call: _e.mock.On(\"GetMinStepDuration\", ctx, params)}\n}\n\nfunc (_c *Reader_GetMinStepDuration_Call) Run(run func(ctx context.Context, params *metricstore.MinStepDurationQueryParameters)) *Reader_GetMinStepDuration_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *metricstore.MinStepDurationQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*metricstore.MinStepDurationQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetMinStepDuration_Call) Return(duration time.Duration, err error) *Reader_GetMinStepDuration_Call {\n\t_c.Call.Return(duration, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetMinStepDuration_Call) RunAndReturn(run func(ctx context.Context, params *metricstore.MinStepDurationQueryParameters) (time.Duration, error)) *Reader_GetMinStepDuration_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/api/samplingstore/empty_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/samplingstore/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\n// Store writes and retrieves sampling data to and from storage.\ntype Store interface {\n\t// InsertThroughput inserts aggregated throughput for operations into storage.\n\tInsertThroughput(throughput []*model.Throughput) error\n\n\t// InsertProbabilitiesAndQPS inserts calculated sampling probabilities and measured qps into storage.\n\tInsertProbabilitiesAndQPS(hostname string, probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS) error\n\n\t// GetThroughput retrieves aggregated throughput for operations within a time range.\n\tGetThroughput(start, end time.Time) ([]*model.Throughput, error)\n\n\t// GetLatestProbabilities retrieves the latest sampling probabilities.\n\tGetLatestProbabilities() (model.ServiceOperationProbabilities, error)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/samplingstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewStore(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Store {\n\tmock := &Store{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Store is an autogenerated mock type for the Store type\ntype Store struct {\n\tmock.Mock\n}\n\ntype Store_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Store) EXPECT() *Store_Expecter {\n\treturn &Store_Expecter{mock: &_m.Mock}\n}\n\n// GetLatestProbabilities provides a mock function for the type Store\nfunc (_mock *Store) GetLatestProbabilities() (model.ServiceOperationProbabilities, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetLatestProbabilities\")\n\t}\n\n\tvar r0 model.ServiceOperationProbabilities\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (model.ServiceOperationProbabilities, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() model.ServiceOperationProbabilities); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(model.ServiceOperationProbabilities)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Store_GetLatestProbabilities_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestProbabilities'\ntype Store_GetLatestProbabilities_Call struct {\n\t*mock.Call\n}\n\n// GetLatestProbabilities is a helper method to define mock.On call\nfunc (_e *Store_Expecter) GetLatestProbabilities() *Store_GetLatestProbabilities_Call {\n\treturn &Store_GetLatestProbabilities_Call{Call: _e.mock.On(\"GetLatestProbabilities\")}\n}\n\nfunc (_c *Store_GetLatestProbabilities_Call) Run(run func()) *Store_GetLatestProbabilities_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Store_GetLatestProbabilities_Call) Return(serviceOperationProbabilities model.ServiceOperationProbabilities, err error) *Store_GetLatestProbabilities_Call {\n\t_c.Call.Return(serviceOperationProbabilities, err)\n\treturn _c\n}\n\nfunc (_c *Store_GetLatestProbabilities_Call) RunAndReturn(run func() (model.ServiceOperationProbabilities, error)) *Store_GetLatestProbabilities_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetThroughput provides a mock function for the type Store\nfunc (_mock *Store) GetThroughput(start time.Time, end time.Time) ([]*model.Throughput, error) {\n\tret := _mock.Called(start, end)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetThroughput\")\n\t}\n\n\tvar r0 []*model.Throughput\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(time.Time, time.Time) ([]*model.Throughput, error)); ok {\n\t\treturn returnFunc(start, end)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(time.Time, time.Time) []*model.Throughput); ok {\n\t\tr0 = returnFunc(start, end)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]*model.Throughput)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(time.Time, time.Time) error); ok {\n\t\tr1 = returnFunc(start, end)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Store_GetThroughput_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetThroughput'\ntype Store_GetThroughput_Call struct {\n\t*mock.Call\n}\n\n// GetThroughput is a helper method to define mock.On call\n//   - start time.Time\n//   - end time.Time\nfunc (_e *Store_Expecter) GetThroughput(start interface{}, end interface{}) *Store_GetThroughput_Call {\n\treturn &Store_GetThroughput_Call{Call: _e.mock.On(\"GetThroughput\", start, end)}\n}\n\nfunc (_c *Store_GetThroughput_Call) Run(run func(start time.Time, end time.Time)) *Store_GetThroughput_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 time.Time\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(time.Time)\n\t\t}\n\t\tvar arg1 time.Time\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(time.Time)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Store_GetThroughput_Call) Return(throughputs []*model.Throughput, err error) *Store_GetThroughput_Call {\n\t_c.Call.Return(throughputs, err)\n\treturn _c\n}\n\nfunc (_c *Store_GetThroughput_Call) RunAndReturn(run func(start time.Time, end time.Time) ([]*model.Throughput, error)) *Store_GetThroughput_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// InsertProbabilitiesAndQPS provides a mock function for the type Store\nfunc (_mock *Store) InsertProbabilitiesAndQPS(hostname string, probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS) error {\n\tret := _mock.Called(hostname, probabilities, qps)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for InsertProbabilitiesAndQPS\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(string, model.ServiceOperationProbabilities, model.ServiceOperationQPS) error); ok {\n\t\tr0 = returnFunc(hostname, probabilities, qps)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Store_InsertProbabilitiesAndQPS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InsertProbabilitiesAndQPS'\ntype Store_InsertProbabilitiesAndQPS_Call struct {\n\t*mock.Call\n}\n\n// InsertProbabilitiesAndQPS is a helper method to define mock.On call\n//   - hostname string\n//   - probabilities model.ServiceOperationProbabilities\n//   - qps model.ServiceOperationQPS\nfunc (_e *Store_Expecter) InsertProbabilitiesAndQPS(hostname interface{}, probabilities interface{}, qps interface{}) *Store_InsertProbabilitiesAndQPS_Call {\n\treturn &Store_InsertProbabilitiesAndQPS_Call{Call: _e.mock.On(\"InsertProbabilitiesAndQPS\", hostname, probabilities, qps)}\n}\n\nfunc (_c *Store_InsertProbabilitiesAndQPS_Call) Run(run func(hostname string, probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS)) *Store_InsertProbabilitiesAndQPS_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\tvar arg1 model.ServiceOperationProbabilities\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(model.ServiceOperationProbabilities)\n\t\t}\n\t\tvar arg2 model.ServiceOperationQPS\n\t\tif args[2] != nil {\n\t\t\targ2 = args[2].(model.ServiceOperationQPS)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Store_InsertProbabilitiesAndQPS_Call) Return(err error) *Store_InsertProbabilitiesAndQPS_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Store_InsertProbabilitiesAndQPS_Call) RunAndReturn(run func(hostname string, probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS) error) *Store_InsertProbabilitiesAndQPS_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// InsertThroughput provides a mock function for the type Store\nfunc (_mock *Store) InsertThroughput(throughput []*model.Throughput) error {\n\tret := _mock.Called(throughput)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for InsertThroughput\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func([]*model.Throughput) error); ok {\n\t\tr0 = returnFunc(throughput)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Store_InsertThroughput_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InsertThroughput'\ntype Store_InsertThroughput_Call struct {\n\t*mock.Call\n}\n\n// InsertThroughput is a helper method to define mock.On call\n//   - throughput []*model.Throughput\nfunc (_e *Store_Expecter) InsertThroughput(throughput interface{}) *Store_InsertThroughput_Call {\n\treturn &Store_InsertThroughput_Call{Call: _e.mock.On(\"InsertThroughput\", throughput)}\n}\n\nfunc (_c *Store_InsertThroughput_Call) Run(run func(throughput []*model.Throughput)) *Store_InsertThroughput_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 []*model.Throughput\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].([]*model.Throughput)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Store_InsertThroughput_Call) Return(err error) *Store_InsertThroughput_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Store_InsertThroughput_Call) RunAndReturn(run func(throughput []*model.Throughput) error) *Store_InsertThroughput_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/api/samplingstore/model/empty_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage model\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/samplingstore/model/sampling.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage model\n\n// Throughput keeps track of the queries an operation received.\ntype Throughput struct {\n\tService       string\n\tOperation     string\n\tCount         int64\n\tProbabilities map[string]struct{}\n}\n\n// ServiceOperationProbabilities contains the sampling probabilities for all operations in a service.\n// ie [service][operation] = probability\ntype ServiceOperationProbabilities map[string]map[string]float64\n\n// ServiceOperationQPS contains the qps for all operations in a service.\n// ie [service][operation] = qps\ntype ServiceOperationQPS map[string]map[string]float64\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/interface.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// ErrTraceNotFound is returned by Reader's GetTrace if no data is found for given trace ID.\nvar ErrTraceNotFound = errors.New(\"trace not found\")\n\n// Writer writes spans to storage.\ntype Writer interface {\n\tWriteSpan(ctx context.Context, span *model.Span) error\n}\n\n// Reader finds and loads traces and other data from storage.\ntype Reader interface {\n\t// GetTrace retrieves the trace with a given id.\n\t//\n\t// If no spans are stored for this trace, it returns ErrTraceNotFound.\n\tGetTrace(ctx context.Context, query GetTraceParameters) (*model.Trace, error)\n\n\t// GetServices returns all service names known to the backend from spans\n\t// within its retention period.\n\tGetServices(ctx context.Context) ([]string, error)\n\n\t// GetOperations returns all operation names for a given service\n\t// known to the backend from spans within its retention period.\n\tGetOperations(ctx context.Context, query OperationQueryParameters) ([]Operation, error)\n\n\t// FindTraces returns all traces matching query parameters. There's currently\n\t// an implementation-dependent abiguity whether all query filters (such as\n\t// multiple tags) must apply to the same span within a trace, or can be satisfied\n\t// by different spans.\n\t//\n\t// If no matching traces are found, the function returns (nil, nil).\n\tFindTraces(ctx context.Context, query *TraceQueryParameters) ([]*model.Trace, error)\n\n\t// FindTraceIDs does the same search as FindTraces, but returns only the list\n\t// of matching trace IDs.\n\t//\n\t// If no matching traces are found, the function returns (nil, nil).\n\tFindTraceIDs(ctx context.Context, query *TraceQueryParameters) ([]model.TraceID, error)\n}\n\n// GetTraceParameters contains parameters of a trace get.\ntype GetTraceParameters struct {\n\tTraceID   model.TraceID\n\tStartTime time.Time // optional\n\tEndTime   time.Time // optional\n}\n\n// TraceQueryParameters contains parameters of a trace query.\ntype TraceQueryParameters struct {\n\tServiceName   string\n\tOperationName string\n\tTags          map[string]string\n\tStartTimeMin  time.Time\n\tStartTimeMax  time.Time\n\tDurationMin   time.Duration\n\tDurationMax   time.Duration\n\tNumTraces     int\n}\n\n// OperationQueryParameters contains parameters of query operations, empty spanKind means get operations for all kinds of span.\ntype OperationQueryParameters struct {\n\tServiceName string\n\tSpanKind    string\n}\n\n// Operation contains operation name and span kind\ntype Operation struct {\n\tName     string\n\tSpanKind string\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/interface_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewWriter creates a new instance of Writer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewWriter(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Writer {\n\tmock := &Writer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Writer is an autogenerated mock type for the Writer type\ntype Writer struct {\n\tmock.Mock\n}\n\ntype Writer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Writer) EXPECT() *Writer_Expecter {\n\treturn &Writer_Expecter{mock: &_m.Mock}\n}\n\n// WriteSpan provides a mock function for the type Writer\nfunc (_mock *Writer) WriteSpan(ctx context.Context, span *model.Span) error {\n\tret := _mock.Called(ctx, span)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteSpan\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *model.Span) error); ok {\n\t\tr0 = returnFunc(ctx, span)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Writer_WriteSpan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteSpan'\ntype Writer_WriteSpan_Call struct {\n\t*mock.Call\n}\n\n// WriteSpan is a helper method to define mock.On call\n//   - ctx context.Context\n//   - span *model.Span\nfunc (_e *Writer_Expecter) WriteSpan(ctx interface{}, span interface{}) *Writer_WriteSpan_Call {\n\treturn &Writer_WriteSpan_Call{Call: _e.mock.On(\"WriteSpan\", ctx, span)}\n}\n\nfunc (_c *Writer_WriteSpan_Call) Run(run func(ctx context.Context, span *model.Span)) *Writer_WriteSpan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *model.Span\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*model.Span)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Writer_WriteSpan_Call) Return(err error) *Writer_WriteSpan_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Writer_WriteSpan_Call) RunAndReturn(run func(ctx context.Context, span *model.Span) error) *Writer_WriteSpan_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewReader creates a new instance of Reader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Reader {\n\tmock := &Reader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Reader is an autogenerated mock type for the Reader type\ntype Reader struct {\n\tmock.Mock\n}\n\ntype Reader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Reader) EXPECT() *Reader_Expecter {\n\treturn &Reader_Expecter{mock: &_m.Mock}\n}\n\n// FindTraceIDs provides a mock function for the type Reader\nfunc (_mock *Reader) FindTraceIDs(ctx context.Context, query *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraceIDs\")\n\t}\n\n\tvar r0 []model.TraceID\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) ([]model.TraceID, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) []model.TraceID); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]model.TraceID)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *spanstore.TraceQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_FindTraceIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraceIDs'\ntype Reader_FindTraceIDs_Call struct {\n\t*mock.Call\n}\n\n// FindTraceIDs is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query *spanstore.TraceQueryParameters\nfunc (_e *Reader_Expecter) FindTraceIDs(ctx interface{}, query interface{}) *Reader_FindTraceIDs_Call {\n\treturn &Reader_FindTraceIDs_Call{Call: _e.mock.On(\"FindTraceIDs\", ctx, query)}\n}\n\nfunc (_c *Reader_FindTraceIDs_Call) Run(run func(ctx context.Context, query *spanstore.TraceQueryParameters)) *Reader_FindTraceIDs_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *spanstore.TraceQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*spanstore.TraceQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraceIDs_Call) Return(traceIDs []model.TraceID, err error) *Reader_FindTraceIDs_Call {\n\t_c.Call.Return(traceIDs, err)\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraceIDs_Call) RunAndReturn(run func(ctx context.Context, query *spanstore.TraceQueryParameters) ([]model.TraceID, error)) *Reader_FindTraceIDs_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// FindTraces provides a mock function for the type Reader\nfunc (_mock *Reader) FindTraces(ctx context.Context, query *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraces\")\n\t}\n\n\tvar r0 []*model.Trace\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) ([]*model.Trace, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) []*model.Trace); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]*model.Trace)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *spanstore.TraceQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_FindTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraces'\ntype Reader_FindTraces_Call struct {\n\t*mock.Call\n}\n\n// FindTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query *spanstore.TraceQueryParameters\nfunc (_e *Reader_Expecter) FindTraces(ctx interface{}, query interface{}) *Reader_FindTraces_Call {\n\treturn &Reader_FindTraces_Call{Call: _e.mock.On(\"FindTraces\", ctx, query)}\n}\n\nfunc (_c *Reader_FindTraces_Call) Run(run func(ctx context.Context, query *spanstore.TraceQueryParameters)) *Reader_FindTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *spanstore.TraceQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*spanstore.TraceQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraces_Call) Return(traces []*model.Trace, err error) *Reader_FindTraces_Call {\n\t_c.Call.Return(traces, err)\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraces_Call) RunAndReturn(run func(ctx context.Context, query *spanstore.TraceQueryParameters) ([]*model.Trace, error)) *Reader_FindTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperations provides a mock function for the type Reader\nfunc (_mock *Reader) GetOperations(ctx context.Context, query spanstore.OperationQueryParameters) ([]spanstore.Operation, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperations\")\n\t}\n\n\tvar r0 []spanstore.Operation\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.OperationQueryParameters) ([]spanstore.Operation, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.OperationQueryParameters) []spanstore.Operation); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]spanstore.Operation)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, spanstore.OperationQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetOperations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperations'\ntype Reader_GetOperations_Call struct {\n\t*mock.Call\n}\n\n// GetOperations is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query spanstore.OperationQueryParameters\nfunc (_e *Reader_Expecter) GetOperations(ctx interface{}, query interface{}) *Reader_GetOperations_Call {\n\treturn &Reader_GetOperations_Call{Call: _e.mock.On(\"GetOperations\", ctx, query)}\n}\n\nfunc (_c *Reader_GetOperations_Call) Run(run func(ctx context.Context, query spanstore.OperationQueryParameters)) *Reader_GetOperations_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 spanstore.OperationQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(spanstore.OperationQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetOperations_Call) Return(operations []spanstore.Operation, err error) *Reader_GetOperations_Call {\n\t_c.Call.Return(operations, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetOperations_Call) RunAndReturn(run func(ctx context.Context, query spanstore.OperationQueryParameters) ([]spanstore.Operation, error)) *Reader_GetOperations_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetServices provides a mock function for the type Reader\nfunc (_mock *Reader) GetServices(ctx context.Context) ([]string, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetServices\")\n\t}\n\n\tvar r0 []string\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]string)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServices'\ntype Reader_GetServices_Call struct {\n\t*mock.Call\n}\n\n// GetServices is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *Reader_Expecter) GetServices(ctx interface{}) *Reader_GetServices_Call {\n\treturn &Reader_GetServices_Call{Call: _e.mock.On(\"GetServices\", ctx)}\n}\n\nfunc (_c *Reader_GetServices_Call) Run(run func(ctx context.Context)) *Reader_GetServices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetServices_Call) Return(strings []string, err error) *Reader_GetServices_Call {\n\t_c.Call.Return(strings, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetServices_Call) RunAndReturn(run func(ctx context.Context) ([]string, error)) *Reader_GetServices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetTrace provides a mock function for the type Reader\nfunc (_mock *Reader) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTrace\")\n\t}\n\n\tvar r0 *model.Trace\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.GetTraceParameters) (*model.Trace, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.GetTraceParameters) *model.Trace); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*model.Trace)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, spanstore.GetTraceParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetTrace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTrace'\ntype Reader_GetTrace_Call struct {\n\t*mock.Call\n}\n\n// GetTrace is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query spanstore.GetTraceParameters\nfunc (_e *Reader_Expecter) GetTrace(ctx interface{}, query interface{}) *Reader_GetTrace_Call {\n\treturn &Reader_GetTrace_Call{Call: _e.mock.On(\"GetTrace\", ctx, query)}\n}\n\nfunc (_c *Reader_GetTrace_Call) Run(run func(ctx context.Context, query spanstore.GetTraceParameters)) *Reader_GetTrace_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 spanstore.GetTraceParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(spanstore.GetTraceParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetTrace_Call) Return(trace *model.Trace, err error) *Reader_GetTrace_Call {\n\t_c.Call.Return(trace, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetTrace_Call) RunAndReturn(run func(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error)) *Reader_GetTrace_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/spanstoremetrics/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstoremetrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/spanstoremetrics/read_metrics.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstoremetrics\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// ReadMetricsDecorator wraps a spanstore.Reader and collects metrics around each read operation.\ntype ReadMetricsDecorator struct {\n\tspanReader           spanstore.Reader\n\tfindTracesMetrics    *queryMetrics\n\tfindTraceIDsMetrics  *queryMetrics\n\tgetTraceMetrics      *queryMetrics\n\tgetServicesMetrics   *queryMetrics\n\tgetOperationsMetrics *queryMetrics\n}\n\ntype queryMetrics struct {\n\tErrors     metrics.Counter `metric:\"requests\" tags:\"result=err\"`\n\tSuccesses  metrics.Counter `metric:\"requests\" tags:\"result=ok\"`\n\tResponses  metrics.Timer   `metric:\"responses\"` // used as a histogram, not necessary for GetTrace\n\tErrLatency metrics.Timer   `metric:\"latency\" tags:\"result=err\"`\n\tOKLatency  metrics.Timer   `metric:\"latency\" tags:\"result=ok\"`\n}\n\nfunc (q *queryMetrics) emit(err error, latency time.Duration, responses int) {\n\tif err != nil {\n\t\tq.Errors.Inc(1)\n\t\tq.ErrLatency.Record(latency)\n\t} else {\n\t\tq.Successes.Inc(1)\n\t\tq.OKLatency.Record(latency)\n\t\tq.Responses.Record(time.Duration(responses))\n\t}\n}\n\n// NewReaderDecorator returns a new ReadMetricsDecorator.\nfunc NewReaderDecorator(spanReader spanstore.Reader, metricsFactory metrics.Factory) *ReadMetricsDecorator {\n\treturn &ReadMetricsDecorator{\n\t\tspanReader:           spanReader,\n\t\tfindTracesMetrics:    buildQueryMetrics(\"find_traces\", metricsFactory),\n\t\tfindTraceIDsMetrics:  buildQueryMetrics(\"find_trace_ids\", metricsFactory),\n\t\tgetTraceMetrics:      buildQueryMetrics(\"get_trace\", metricsFactory),\n\t\tgetServicesMetrics:   buildQueryMetrics(\"get_services\", metricsFactory),\n\t\tgetOperationsMetrics: buildQueryMetrics(\"get_operations\", metricsFactory),\n\t}\n}\n\nfunc buildQueryMetrics(operation string, metricsFactory metrics.Factory) *queryMetrics {\n\tqMetrics := &queryMetrics{}\n\tscoped := metricsFactory.Namespace(metrics.NSOptions{Name: \"\", Tags: map[string]string{\"operation\": operation}})\n\tmetrics.Init(qMetrics, scoped, nil)\n\treturn qMetrics\n}\n\n// FindTraces implements spanstore.Reader#FindTraces\nfunc (m *ReadMetricsDecorator) FindTraces(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\tstart := time.Now()\n\tretMe, err := m.spanReader.FindTraces(ctx, traceQuery)\n\tm.findTracesMetrics.emit(err, time.Since(start), len(retMe))\n\treturn retMe, err\n}\n\n// FindTraceIDs implements spanstore.Reader#FindTraceIDs\nfunc (m *ReadMetricsDecorator) FindTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\tstart := time.Now()\n\tretMe, err := m.spanReader.FindTraceIDs(ctx, traceQuery)\n\tm.findTraceIDsMetrics.emit(err, time.Since(start), len(retMe))\n\treturn retMe, err\n}\n\n// GetTrace implements spanstore.Reader#GetTrace\nfunc (m *ReadMetricsDecorator) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\tstart := time.Now()\n\tretMe, err := m.spanReader.GetTrace(ctx, query)\n\tm.getTraceMetrics.emit(err, time.Since(start), 1)\n\treturn retMe, err\n}\n\n// GetServices implements spanstore.Reader#GetServices\nfunc (m *ReadMetricsDecorator) GetServices(ctx context.Context) ([]string, error) {\n\tstart := time.Now()\n\tretMe, err := m.spanReader.GetServices(ctx)\n\tm.getServicesMetrics.emit(err, time.Since(start), len(retMe))\n\treturn retMe, err\n}\n\n// GetOperations implements spanstore.Reader#GetOperations\nfunc (m *ReadMetricsDecorator) GetOperations(\n\tctx context.Context,\n\tquery spanstore.OperationQueryParameters,\n) ([]spanstore.Operation, error) {\n\tstart := time.Now()\n\tretMe, err := m.spanReader.GetOperations(ctx, query)\n\tm.getOperationsMetrics.emit(err, time.Since(start), len(retMe))\n\treturn retMe, err\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/spanstoremetrics/read_metrics_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstoremetrics_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/spanstoremetrics\"\n)\n\nfunc TestSuccessfulUnderlyingCalls(t *testing.T) {\n\tmf := metricstest.NewFactory(0)\n\n\tmockReader := mocks.Reader{}\n\tmrs := spanstoremetrics.NewReaderDecorator(&mockReader, mf)\n\tmockReader.On(\"GetServices\", context.Background()).Return([]string{}, nil)\n\tmrs.GetServices(context.Background())\n\toperationQuery := spanstore.OperationQueryParameters{ServiceName: \"something\"}\n\tmockReader.On(\"GetOperations\", context.Background(), operationQuery).\n\t\tReturn([]spanstore.Operation{}, nil)\n\tmrs.GetOperations(context.Background(), operationQuery)\n\tmockReader.On(\"GetTrace\", context.Background(), spanstore.GetTraceParameters{}).Return(&model.Trace{}, nil)\n\tmrs.GetTrace(context.Background(), spanstore.GetTraceParameters{})\n\tmockReader.On(\"FindTraces\", context.Background(), &spanstore.TraceQueryParameters{}).\n\t\tReturn([]*model.Trace{}, nil)\n\tmrs.FindTraces(context.Background(), &spanstore.TraceQueryParameters{})\n\tmockReader.On(\"FindTraceIDs\", context.Background(), &spanstore.TraceQueryParameters{}).\n\t\tReturn([]model.TraceID{}, nil)\n\tmrs.FindTraceIDs(context.Background(), &spanstore.TraceQueryParameters{})\n\tcounters, gauges := mf.Snapshot()\n\texpecteds := map[string]int64{\n\t\t\"requests|operation=get_operations|result=ok\":  1,\n\t\t\"requests|operation=get_operations|result=err\": 0,\n\t\t\"requests|operation=get_trace|result=ok\":       1,\n\t\t\"requests|operation=get_trace|result=err\":      0,\n\t\t\"requests|operation=find_traces|result=ok\":     1,\n\t\t\"requests|operation=find_traces|result=err\":    0,\n\t\t\"requests|operation=find_trace_ids|result=ok\":  1,\n\t\t\"requests|operation=find_trace_ids|result=err\": 0,\n\t\t\"requests|operation=get_services|result=ok\":    1,\n\t\t\"requests|operation=get_services|result=err\":   0,\n\t}\n\n\texistingKeys := []string{\n\t\t\"latency|operation=get_operations|result=ok.P50\",\n\t\t\"responses|operation=get_trace.P50\",\n\t\t\"latency|operation=find_traces|result=ok.P50\", // this is not exhaustive\n\t}\n\tnonExistentKeys := []string{\n\t\t\"latency|operation=get_operations|result=err.P50\",\n\t}\n\n\tcheckExpectedExistingAndNonExistentCounters(t, counters, expecteds, gauges, existingKeys, nonExistentKeys)\n}\n\nfunc checkExpectedExistingAndNonExistentCounters(t *testing.T,\n\tactualCounters,\n\texpectedCounters,\n\tactualGauges map[string]int64,\n\texistingKeys,\n\tnonExistentKeys []string,\n) {\n\tfor k, v := range expectedCounters {\n\t\tassert.Equal(t, v, actualCounters[k], k)\n\t}\n\n\tfor _, k := range existingKeys {\n\t\t_, ok := actualGauges[k]\n\t\tassert.True(t, ok)\n\t}\n\n\tfor _, k := range nonExistentKeys {\n\t\t_, ok := actualGauges[k]\n\t\tassert.False(t, ok)\n\t}\n}\n\nfunc TestFailingUnderlyingCalls(t *testing.T) {\n\tmf := metricstest.NewFactory(0)\n\n\tmockReader := mocks.Reader{}\n\tmrs := spanstoremetrics.NewReaderDecorator(&mockReader, mf)\n\tmockReader.On(\"GetServices\", context.Background()).\n\t\tReturn(nil, errors.New(\"Failure\"))\n\tmrs.GetServices(context.Background())\n\toperationQuery := spanstore.OperationQueryParameters{ServiceName: \"something\"}\n\tmockReader.On(\"GetOperations\", context.Background(), operationQuery).\n\t\tReturn(nil, errors.New(\"Failure\"))\n\tmrs.GetOperations(context.Background(), operationQuery)\n\tmockReader.On(\"GetTrace\", context.Background(), spanstore.GetTraceParameters{}).\n\t\tReturn(nil, errors.New(\"Failure\"))\n\tmrs.GetTrace(context.Background(), spanstore.GetTraceParameters{})\n\tmockReader.On(\"FindTraces\", context.Background(), &spanstore.TraceQueryParameters{}).\n\t\tReturn(nil, errors.New(\"Failure\"))\n\tmrs.FindTraces(context.Background(), &spanstore.TraceQueryParameters{})\n\tmockReader.On(\"FindTraceIDs\", context.Background(), &spanstore.TraceQueryParameters{}).\n\t\tReturn(nil, errors.New(\"Failure\"))\n\tmrs.FindTraceIDs(context.Background(), &spanstore.TraceQueryParameters{})\n\tcounters, gauges := mf.Snapshot()\n\texpecteds := map[string]int64{\n\t\t\"requests|operation=get_operations|result=ok\":  0,\n\t\t\"requests|operation=get_operations|result=err\": 1,\n\t\t\"requests|operation=get_trace|result=ok\":       0,\n\t\t\"requests|operation=get_trace|result=err\":      1,\n\t\t\"requests|operation=find_traces|result=ok\":     0,\n\t\t\"requests|operation=find_traces|result=err\":    1,\n\t\t\"requests|operation=find_trace_ids|result=ok\":  0,\n\t\t\"requests|operation=find_trace_ids|result=err\": 1,\n\t\t\"requests|operation=get_services|result=ok\":    0,\n\t\t\"requests|operation=get_services|result=err\":   1,\n\t}\n\n\texistingKeys := []string{\n\t\t\"latency|operation=get_operations|result=err.P50\",\n\t}\n\n\tnonExistentKeys := []string{\n\t\t\"latency|operation=get_operations|result=ok.P50\",\n\t\t\"responses|operation=get_trace.P50\",\n\t\t\"latency|operation=query|result=ok.P50\", // this is not exhaustive\n\t}\n\n\tcheckExpectedExistingAndNonExistentCounters(t, counters, expecteds, gauges, existingKeys, nonExistentKeys)\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/spanstoremetrics/write_metrics.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstoremetrics\n\nimport (\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\n// WriteMetrics is a collection of metrics for write operations.\ntype WriteMetrics struct {\n\tAttempts   metrics.Counter `metric:\"attempts\"`\n\tInserts    metrics.Counter `metric:\"inserts\"`\n\tErrors     metrics.Counter `metric:\"errors\"`\n\tLatencyOk  metrics.Timer   `metric:\"latency-ok\"`\n\tLatencyErr metrics.Timer   `metric:\"latency-err\"`\n}\n\n// NewWriter takes a metrics scope and creates a metrics struct\nfunc NewWriter(factory metrics.Factory, tableName string) *WriteMetrics {\n\tt := &WriteMetrics{}\n\tmetrics.Init(t, factory.Namespace(metrics.NSOptions{Name: tableName, Tags: nil}), nil)\n\treturn t\n}\n\n// Emit will record success or failure counts and latency metrics depending on the passed error.\nfunc (t *WriteMetrics) Emit(err error, latency time.Duration) {\n\tt.Attempts.Inc(1)\n\tif err != nil {\n\t\tt.LatencyErr.Record(latency)\n\t\tt.Errors.Inc(1)\n\t} else {\n\t\tt.LatencyOk.Record(latency)\n\t\tt.Inserts.Inc(1)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/api/spanstore/spanstoremetrics/write_metrics_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstoremetrics\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n)\n\nfunc TestTableEmit(t *testing.T) {\n\ttestCases := []struct {\n\t\terr    error\n\t\tcounts map[string]int64\n\t\tgauges map[string]int64\n\t}{\n\t\t{\n\t\t\terr: nil,\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"a_table.attempts\": 1,\n\t\t\t\t\"a_table.inserts\":  1,\n\t\t\t},\n\t\t\tgauges: map[string]int64{\n\t\t\t\t\"a_table.latency-ok.P999\": 50,\n\t\t\t\t\"a_table.latency-ok.P50\":  50,\n\t\t\t\t\"a_table.latency-ok.P75\":  50,\n\t\t\t\t\"a_table.latency-ok.P90\":  50,\n\t\t\t\t\"a_table.latency-ok.P95\":  50,\n\t\t\t\t\"a_table.latency-ok.P99\":  50,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"some error\"),\n\t\t\tcounts: map[string]int64{\n\t\t\t\t\"a_table.attempts\": 1,\n\t\t\t\t\"a_table.errors\":   1,\n\t\t\t},\n\t\t\tgauges: map[string]int64{\n\t\t\t\t\"a_table.latency-err.P999\": 50,\n\t\t\t\t\"a_table.latency-err.P50\":  50,\n\t\t\t\t\"a_table.latency-err.P75\":  50,\n\t\t\t\t\"a_table.latency-err.P90\":  50,\n\t\t\t\t\"a_table.latency-err.P95\":  50,\n\t\t\t\t\"a_table.latency-err.P99\":  50,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tmf := metricstest.NewFactory(time.Second)\n\t\ttm := NewWriter(mf, \"a_table\")\n\t\ttm.Emit(tc.err, 50*time.Millisecond)\n\t\tcounts, gauges := mf.Snapshot()\n\t\tassert.Equal(t, tc.counts, counts)\n\t\tassert.Equal(t, tc.gauges, gauges)\n\t\tmf.Stop()\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/README.md",
    "content": "# Badger data storage\n\n## Data modeling\n\nThe key design in badger storage backend takes advantage of sorted nature of badger as well as the key only searching with badger, which does not require loading the values from the value log but only the keys in the LSM tree. This is used to implement efficient inverted index for both lookups as well as range searches. All the values in the keys must be stored in big endian ordering to make the sorting work properly.\n\nIndex keys structure is created in the ``createIndexKey`` in ``spanstore/writer.go`` and the primary key for spans in ``createTraceKV``.\n\n### Primary key design\n\nPrimary keys are the only keys that have a value in the badger's storage. Each key presents a single span, thus a single trace is a collection of tuples. The value is the actual span, which is marshalled into bytes. The marshalling format is indicated by the last 4 bits of the meta encoding byte in the badger entry. \n\nPrimary keys are sorted as follows:\n\n* TraceID High\n* TraceID Low\n* Timestamp\n* SpanID\n\nThis allows quick lookup for a single TraceID by searching for prefix with: 0x80 + traceID high + traceID low and then iterating as long as that prefix is valid. Note that timestamp ordering does not allow fetching a range of traces in a time range. \n\n### Index key design\n\nEach index key has a single byte first to indicate which field is indexed. The last 4 bits of the first byte in the key are used to indicate which index key is used, with the first 4 bits being zeroed. This sorts the LSM tree by index field which allows quicker range queries. Each inverted index key is then sorted in the following order:\n\n* Value\n* Timestamp\n* TraceID High\n* TraceID Low\n\nThat means the scanning for a single value can continue until we reach the first timestamp which is not in the boundaries and then stop since we can guarantee the future keys are not going to be valid. \n\n## Index searches\n\nIf the lookup is a single traceID, the logic mentioned in the ``Primary key design`` section is used. If instead we have a TraceQueryParameters with one or more search keys to use, we need to combine the results of multiple index seeks to form an intersection of those results. Each search parameter (each tag is new search parameter) is used to scan single index key, thus we iterate the index until the ``<indexKey><value><timestamp>`` is no longer valid. We do this by checking the prefix for ``<indexKey><value>`` for exactness and then ``<timestamp>`` for range. As long as that one is valid, we fetch the keys. Once the timestamp goes beyond our maximum timestamp, the iteration stops. The keys are then sorted to ``TraceID`` order instead of their natural key ordering for the next part.\n\nException to the above is the duration index, since there are no exact duration values but a range of values. When scanning it, the prefix search lookups the starting point with ``<indexKey><minDurationValue>`` and scans the index until ``<indexKey><maxDurationValue>`` is reached. Each key is then separately checked for valid ``<timestamp>`` but the timestamp does not control the seek process and some keys are ignored because they did not match the given time range. \n\nBecause each TraceID is stored as spans, the same TraceID can appear multiple times from a index query. Other than duration query, this means they are coming in order so each of them is discarded by easily checking if the previous one is equal to current one, but with the duration index the spans can come in random order and thus hash-join is used to filter the duplicates.\n\nAfter all the index keys have been scanned, the process is then sent to the merge-join where two index queries are compared and only matching IDs are taken. After that, the next one is compared to the result of the previous and so forth until all the index fetches have been processed. The resulting query set is the list of TraceIDs that matched all the requirements. "
  },
  {
    "path": "internal/storage/v1/badger/config.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/asaskevich/govalidator\"\n)\n\nconst (\n\tdefaultMaintenanceInterval   time.Duration = 5 * time.Minute\n\tdefaultMetricsUpdateInterval time.Duration = 10 * time.Second\n\tdefaultTTL                   time.Duration = time.Hour * 72\n\tdefaultDataDir               string        = string(os.PathSeparator) + \"data\"\n)\n\nvar (\n\tdefaultValueDir string = filepath.Join(defaultDataDir, \"values\")\n\tdefaultKeysDir  string = filepath.Join(defaultDataDir, \"keys\")\n)\n\n// Config is badger's internal configuration data.\ntype Config struct {\n\t// TTL holds time-to-live configuration for the badger store.\n\tTTL TTL `mapstructure:\"ttl\"`\n\t// Directories contains the configuration for where items are stored. Ephemeral must be\n\t// set to false for this configuration to take effect.\n\tDirectories Directories `mapstructure:\"directories\"`\n\t// Ephemeral, if set to true, will store data in a temporary file system.\n\t// If set to true, the configuration in Directories is ignored.\n\tEphemeral bool `mapstructure:\"ephemeral\"`\n\t// SyncWrites, if set to true, will immediately sync all writes to disk. Note that\n\t// setting this field to true will affect write performance.\n\tSyncWrites bool `mapstructure:\"consistency\"`\n\t// MaintenanceInterval is the regular interval after which a maintenance job is\n\t// run on the values in the store.\n\tMaintenanceInterval time.Duration `mapstructure:\"maintenance_interval\"`\n\t// MetricsUpdateInterval is the regular interval after which metrics are collected\n\t// by Jaeger.\n\tMetricsUpdateInterval time.Duration `mapstructure:\"metrics_update_interval\"`\n\t// ReadOnly opens the data store in read-only mode. Multiple instances can open the same\n\t// store in read-only mode. Values still in the write-ahead-log must be replayed before opening.\n\tReadOnly bool `mapstructure:\"read_only\"`\n}\n\ntype TTL struct {\n\t// SpanStore holds the amount of time that the span store data is stored.\n\t// Once this duration has passed for a given key, span store data will\n\t// no longer be accessible.\n\tSpans time.Duration `mapstructure:\"spans\"`\n}\n\ntype Directories struct {\n\t// Keys contains the directory in which the keys are stored.\n\tKeys string `mapstructure:\"keys\"`\n\t// Values contains the directory in which the values are stored.\n\tValues string `mapstructure:\"values\"`\n}\n\nfunc DefaultConfig() *Config {\n\tdefaultBadgerDataDir := getCurrentExecutableDir()\n\treturn &Config{\n\t\tTTL: TTL{\n\t\t\tSpans: defaultTTL,\n\t\t},\n\t\tSyncWrites: false, // Performance over durability\n\t\tEphemeral:  true,  // Default is ephemeral storage\n\t\tDirectories: Directories{\n\t\t\tKeys:   defaultBadgerDataDir + defaultKeysDir,\n\t\t\tValues: defaultBadgerDataDir + defaultValueDir,\n\t\t},\n\t\tMaintenanceInterval:   defaultMaintenanceInterval,\n\t\tMetricsUpdateInterval: defaultMetricsUpdateInterval,\n\t}\n}\n\nfunc getCurrentExecutableDir() string {\n\t// We ignore the error, this will fail later when trying to start the store\n\texec, _ := os.Executable()\n\treturn filepath.Dir(exec)\n}\n\nfunc (c *Config) Validate() error {\n\t_, err := govalidator.ValidateStruct(c)\n\treturn err\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/config_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidate_DoesNotReturnErrorWhenValid(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  *Config\n\t}{\n\t\t{\n\t\t\tname: \"non-required fields not set\",\n\t\t\tcfg:  &Config{},\n\t\t},\n\t\t{\n\t\t\tname: \"all fields are set\",\n\t\t\tcfg: &Config{\n\t\t\t\tTTL: TTL{\n\t\t\t\t\tSpans: time.Second,\n\t\t\t\t},\n\t\t\t\tDirectories: Directories{\n\t\t\t\t\tKeys:   \"some-key-directory\",\n\t\t\t\t\tValues: \"some-values-directory\",\n\t\t\t\t},\n\t\t\t\tEphemeral:             false,\n\t\t\t\tSyncWrites:            false,\n\t\t\t\tMaintenanceInterval:   time.Second,\n\t\t\t\tMetricsUpdateInterval: time.Second,\n\t\t\t\tReadOnly:              false,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\terr := test.cfg.Validate()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/dependencystore/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/dependencystore/storage.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// DependencyStore handles all queries and insertions to Badger dependencies\ntype DependencyStore struct {\n\treader spanstore.Reader\n}\n\n// NewDependencyStore returns a DependencyStore\nfunc NewDependencyStore(store spanstore.Reader) *DependencyStore {\n\treturn &DependencyStore{\n\t\treader: store,\n\t}\n}\n\n// GetDependencies returns all interservice dependencies, implements DependencyReader\nfunc (s *DependencyStore) GetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error) {\n\tdeps := map[string]*model.DependencyLink{}\n\n\tparams := &spanstore.TraceQueryParameters{\n\t\tStartTimeMin: endTs.Add(-1 * lookback),\n\t\tStartTimeMax: endTs,\n\t}\n\n\t// We need to do a full table scan - if this becomes a bottleneck, we can write an index that describes\n\t// dependencyKeyPrefix + timestamp + parent + child key and do a key-only seek (which is fast - but requires additional writes)\n\n\t// GetDependencies is not shipped with a context like the SpanReader / SpanWriter\n\ttraces, err := s.reader.FindTraces(ctx, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, tr := range traces {\n\t\tprocessTrace(deps, tr)\n\t}\n\n\treturn depMapToSlice(deps), err\n}\n\n// depMapToSlice modifies the spans to DependencyLink in the same way as the memory storage plugin\nfunc depMapToSlice(deps map[string]*model.DependencyLink) []model.DependencyLink {\n\tretMe := make([]model.DependencyLink, 0, len(deps))\n\tfor _, dep := range deps {\n\t\tretMe = append(retMe, *dep)\n\t}\n\treturn retMe\n}\n\n// processTrace is copy from the memory storage plugin\nfunc processTrace(deps map[string]*model.DependencyLink, trace *model.Trace) {\n\tfor _, s := range trace.Spans {\n\t\tparentSpan := seekToSpan(trace, s.ParentSpanID())\n\t\tif parentSpan != nil {\n\t\t\tif parentSpan.Process.ServiceName == s.Process.ServiceName {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdepKey := parentSpan.Process.ServiceName + \"&&&\" + s.Process.ServiceName\n\t\t\tif _, ok := deps[depKey]; !ok {\n\t\t\t\tdeps[depKey] = &model.DependencyLink{\n\t\t\t\t\tParent:    parentSpan.Process.ServiceName,\n\t\t\t\t\tChild:     s.Process.ServiceName,\n\t\t\t\t\tCallCount: 1,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdeps[depKey].CallCount++\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc seekToSpan(trace *model.Trace, spanID model.SpanID) *model.Span {\n\tfor _, s := range trace.Spans {\n\t\tif s.SpanID == spanID {\n\t\t\treturn s\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/dependencystore/storage_internal_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nfunc TestSeekToSpan(t *testing.T) {\n\tspan := seekToSpan(&model.Trace{}, model.SpanID(uint64(1)))\n\tassert.Nil(t, span)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/dependencystore/storage_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n)\n\n// Opens a badger db and runs a test on it.\nfunc runFactoryTest(tb testing.TB, test func(tb testing.TB, sw spanstore.Writer, dr dependencystore.Reader)) {\n\tf := badger.NewFactory()\n\tf.Config.Ephemeral = true\n\tf.Config.SyncWrites = false\n\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\trequire.NoError(tb, err)\n\tdefer func() {\n\t\trequire.NoError(tb, f.Close())\n\t}()\n\n\tsw, err := f.CreateSpanWriter()\n\trequire.NoError(tb, err)\n\n\tdr, err := f.CreateDependencyReader()\n\trequire.NoError(tb, err)\n\n\ttest(tb, sw, dr)\n}\n\nfunc TestDependencyReader(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, sw spanstore.Writer, dr dependencystore.Reader) {\n\t\ttid := time.Now()\n\t\tlinks, err := dr.GetDependencies(context.Background(), tid, time.Hour)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, links)\n\n\t\ttraces := 40\n\t\tspans := 3\n\t\tfor i := range traces {\n\t\t\tfor j := range spans {\n\t\t\t\ts := model.Span{\n\t\t\t\t\tTraceID: model.TraceID{\n\t\t\t\t\t\tLow:  uint64(i),\n\t\t\t\t\t\tHigh: 1,\n\t\t\t\t\t},\n\t\t\t\t\tSpanID:        model.SpanID(j),\n\t\t\t\t\tOperationName: \"operation-a\",\n\t\t\t\t\tProcess: &model.Process{\n\t\t\t\t\t\tServiceName: fmt.Sprintf(\"service-%d\", j),\n\t\t\t\t\t},\n\t\t\t\t\tStartTime: tid.Add(time.Duration(i)),\n\t\t\t\t\tDuration:  time.Duration(i + j),\n\t\t\t\t}\n\t\t\t\tif j > 0 {\n\t\t\t\t\ts.References = []model.SpanRef{model.NewChildOfRef(s.TraceID, model.SpanID(j-1))}\n\t\t\t\t}\n\t\t\t\terr := sw.WriteSpan(context.Background(), &s)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\t\tlinks, err = dr.GetDependencies(context.Background(), time.Now(), time.Hour)\n\t\trequire.NoError(t, err)\n\t\tassert.NotEmpty(t, links)\n\t\tassert.Len(t, links, spans-1)                       // First span does not create a dependency\n\t\tassert.Equal(t, uint64(traces), links[0].CallCount) // Each trace calls the same services\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/docs/storage-file-non-root-permission.md",
    "content": "# Badger file permissions as non-root service\n\nAfter the release of 1.50, Jaeger's Docker image is no longer running with root privileges (in [#4783](https://github.com/jaegertracing/jaeger/pull/4783)). In some installations it may cause issues such as \"permission denied\" errors when writing data.\n\nA possible workaround for this ([proposed here](https://github.com/jaegertracing/jaeger/issues/4906#issuecomment-1991779425)) is to run an initialization step as `root` that pre-creates the Badger data directory and updates its owner to the user that will run the main Jaeger process.\n\n```yaml\nversion: \"3.9\"\n\nservices:\n[...]\n  jaeger:\n    image: jaegertracing/all-in-one:latest\n    command:\n      - \"--badger.ephemeral=false\"\n      - \"--badger.directory-key=/badger/data/keys\"\n      - \"--badger.directory-value=/badger/data/values\"\n      - \"--badger.span-store-ttl=72h0m0s\" # limit storage to 72hrs\n    environment:\n      - SPAN_STORAGE_TYPE=badger\n    # Mount host directory \"jaeger_badger_data\" as \"/badger\" inside the container.\n    # The actual data directory will be \"/badger/data\", \n    # since we cannot change permissions on the mount.\n    volumes:\n      - jaeger_badger_data:/badger\n    ports:\n      - \"16686:16686\"\n      - \"14250\"\n      - \"4317\"\n    depends_on:\n      prepare-data-dir:\n        condition: service_completed_successfully\n\n  prepare-data-dir:\n    # Run this step as root so that we can change the directory owner.\n    user: root\n    image: jaegertracing/all-in-one:latest\n    command: \"/bin/sh -c 'mkdir -p /badger/data && touch /badger/data/.initialized && chown -R 10001:10001 /badger/data'\"\n    volumes:\n      - jaeger_badger_data:/badger\n\nvolumes:\n  jaeger_badger_data:\n```\n"
  },
  {
    "path": "internal/storage/v1/badger/docs/upgrade-v1-to-v3.md",
    "content": "# Upgrade Badger v1 to v3\n\nIn Jaeger 1.24.0, Badger is upgraded from v1.6.2 to v3.2103.0 which changes the underlying data format. Following steps will help in migrating your data:\n\n1. In Badger v1, the data looks like:\n\n```sh\n❯ ls /tmp/badger/\ndata  key\n❯ ls /tmp/badger/data/\n000001.vlog  000004.vlog  000005.vlog  000008.vlog  000011.vlog  000012.vlog  000013.vlog  000014.vlog  000015.vlog  000016.vlog  000017.vlog\n❯ ls /tmp/badger/key/\n000038.sst  000048.sst  000049.sst  000050.sst  000051.sst  000059.sst  000060.sst  000061.sst  000063.sst  000064.sst  000065.sst  000066.sst  MANIFEST\n```\n\n2. Make a backup of your data directory to have a copy incase migration didn't work successfully.\n\n```sh\n❯ cp -r /tmp/badger /tmp/badger.bk\n```\n\n3. Download, extract and compile the source code of badger v1: https://github.com/dgraph-io/badger/archive/refs/tags/v1.6.2.tar.gz\n\n```sh\n❯ tar xvzf badger-1.6.2.tar\n❯ cd badger-1.6.2/badger/\n❯ go install\n```\n\nThis will install the badger command line utility into your $GOBIN path eg ~/go/bin/badger.\n\n4. Use badger utility to take backup of data.\n\n```sh\n❯ ~/go/bin/badger backup --dir /tmp/badger/key --vlog-dir /tmp/badger/data/\nListening for /debug HTTP requests at port: 8080\nbadger 2021/06/24 22:04:30 INFO: All 12 tables opened in 907ms\nbadger 2021/06/24 22:04:30 INFO: Replaying file id: 17 at offset: 64584535\nbadger 2021/06/24 22:04:30 INFO: Replay took: 12.303µs\nbadger 2021/06/24 22:04:30 DEBUG: Value log discard stats empty\nbadger 2021/06/24 22:04:30 INFO: DB.Backup Created batch of size: 9.7 kB in 75.907µs.\nbadger 2021/06/24 22:04:31 INFO: DB.Backup Created batch of size: 4.3 MB in 8.003592ms.\n....\n....\nbadger 2021/06/24 22:04:31 INFO: DB.Backup Created batch of size: 30 MB in 74.808075ms.\nbadger 2021/06/24 22:04:36 INFO: DB.Backup Sent 15495232 keys\nbadger 2021/06/24 22:04:36 INFO: Got compaction priority: {level:0 score:1.73 dropPrefixes:[]}\n```\n\nThis will create a badger.bak file in the current directory.\n\n5. Download, extract and compile the source code of badger v3: https://github.com/dgraph-io/badger/archive/refs/tags/v3.2103.0.tar.gz\n\n```sh\n❯ tar xvzf badger-3.2103.0.tar\n❯ cd badger-3.2103.0/badger/\n❯ go install\n```\n\nThis will install the badger command line utility into your $GOBIN path eg ~/go/bin/badger.\n\n6. Restore the data from backup.\n\n```sh\n❯ ~/go/bin/badger restore --dir jaeger-v3\nListening for /debug HTTP requests at port: 8080\njemalloc enabled: false\nUsing Go memory\nbadger 2021/06/24 22:08:29 INFO: All 0 tables opened in 0s\nbadger 2021/06/24 22:08:29 INFO: Discard stats nextEmptySlot: 0\nbadger 2021/06/24 22:08:29 INFO: Set nextTxnTs to 0\nbadger 2021/06/24 22:08:37 INFO: [0] [E] LOG Compact 0->6 (5, 0 -> 50 tables with 1 splits). [00001 00002 00003 00004 00005 . .] -> [00006 00007 00008 00009 00010 00011 00012 00013 00014 00015 00016 00017 00018 00019 00020 00021 00022 00023 00024 00025 00026 00028 00029 00030 00031 00032 00033 00034 00035 00036 00037 00038 00039 00040 00041 00043 00044 00045 00046 00047 00048 00049 00050 00051 00052 00053 00054 00055 00056 00057 .], took 2.597s\nbadger 2021/06/24 22:08:53 INFO: Lifetime L0 stalled for: 0s\nbadger 2021/06/24 22:08:55 INFO:\nLevel 0 [ ]: NumTables: 00. Size: 0 B of 0 B. Score: 0.00->0.00 StaleData: 0 B Target FileSize: 64 MiB\nLevel 1 [ ]: NumTables: 00. Size: 0 B of 10 MiB. Score: 0.00->0.00 StaleData: 0 B Target FileSize: 2.0 MiB\nLevel 2 [ ]: NumTables: 00. Size: 0 B of 10 MiB. Score: 0.00->0.00 StaleData: 0 B Target FileSize: 2.0 MiB\nLevel 3 [ ]: NumTables: 00. Size: 0 B of 10 MiB. Score: 0.00->0.00 StaleData: 0 B Target FileSize: 2.0 MiB\nLevel 4 [B]: NumTables: 45. Size: 86 MiB of 10 MiB. Score: 8.64->10.21 StaleData: 0 B Target FileSize: 2.0 MiB\nLevel 5 [ ]: NumTables: 08. Size: 29 MiB of 34 MiB. Score: 0.00->0.00 StaleData: 0 B Target FileSize: 4.0 MiB\nLevel 6 [ ]: NumTables: 63. Size: 340 MiB of 340 MiB. Score: 0.00->0.00 StaleData: 0 B Target FileSize: 8.0 MiB\nLevel Done\nNum Allocated Bytes at program end: 0 B\n```\n\nThis will restore the data in jaeger-v3 directory. It will look like this\n\n```sh\n❯ ls ./jaeger-v3\n000001.vlog  000180.sst  000257.sst  000276.sst  000294.sst  000327.sst  000336.sst  000349.sst  000356.sst  000364.sst  000371.sst  000378.sst  000385.sst  000392.sst  000399.sst  000406.sst  000413.sst   MANIFEST\n000006.sst   000181.sst  000259.sst  000277.sst  000302.sst  000328.sst  000339.sst  000350.sst  000357.sst  000365.sst  000372.sst  000379.sst  000386.sst  000393.sst  000400.sst  000407.sst  000414.sst\n000007.sst   000195.sst  000261.sst  000278.sst  000305.sst  000330.sst  000340.sst  000351.sst  000359.sst  000366.sst  000373.sst  000380.sst  000387.sst  000394.sst  000401.sst  000408.sst  000415.sst\n000008.sst   000218.sst  000265.sst  000279.sst  000315.sst  000331.sst  000341.sst  000352.sst  000360.sst  000367.sst  000374.sst  000381.sst  000388.sst  000395.sst  000402.sst  000409.sst  000416.sst\n000061.sst   000227.sst  000267.sst  000282.sst  000324.sst  000332.sst  000343.sst  000353.sst  000361.sst  000368.sst  000375.sst  000382.sst  000389.sst  000396.sst  000403.sst  000410.sst  000417.sst\n000134.sst   000249.sst  000272.sst  000285.sst  000325.sst  000333.sst  000344.sst  000354.sst  000362.sst  000369.sst  000376.sst  000383.sst  000390.sst  000397.sst  000404.sst  000411.sst  DISCARD\n000154.sst   000255.sst  000275.sst  000289.sst  000326.sst  000334.sst  000348.sst  000355.sst  000363.sst  000370.sst  000377.sst  000384.sst  000391.sst  000398.sst  000405.sst  000412.sst  KEYREGISTRY\n```\n\n7. Separate out the key and data directories.\n\n```sh\n❯ rm -rf /tmp/badger\n❯ mv ./jaeger-v3 /tmp/badger\n❯ mkdir /tmp/badger/data /tmp/badger/key\n❯ mv /tmp/badger/*.vlog /tmp/badger/data/\n❯ mv /tmp/badger/*.sst /tmp/badger/key/\n❯ mv /tmp/badger/MANIFEST /tmp/badger/DISCARD /tmp/badger/KEYREGISTRY /tmp/badger/key/\n```\n\n8. Start Jaeger v1.24.0. It should start well.\n"
  },
  {
    "path": "internal/storage/v1/badger/factory.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"expvar\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/spanstoremetrics\"\n\tdepstore \"github.com/jaegertracing/jaeger/internal/storage/v1/badger/dependencystore\"\n\tbadgersampling \"github.com/jaegertracing/jaeger/internal/storage/v1/badger/samplingstore\"\n\tbadgerstore \"github.com/jaegertracing/jaeger/internal/storage/v1/badger/spanstore\"\n)\n\nconst (\n\tvalueLogSpaceAvailableName = \"badger_value_log_bytes_available\"\n\tkeyLogSpaceAvailableName   = \"badger_key_log_bytes_available\"\n\tlastMaintenanceRunName     = \"badger_storage_maintenance_last_run\"\n\tlastValueLogCleanedName    = \"badger_storage_valueloggc_last_run\"\n)\n\nvar ( // interface comformance checks\n\t_ io.Closer                    = (*Factory)(nil)\n\t_ storage.Purger               = (*Factory)(nil)\n\t_ storage.SamplingStoreFactory = (*Factory)(nil)\n)\n\n// Factory for Badger backend.\ntype Factory struct {\n\tConfig         *Config\n\tstore          *badger.DB\n\tcache          *badgerstore.CacheStore\n\tlogger         *zap.Logger\n\tmetricsFactory metrics.Factory\n\n\ttmpDir          string\n\tmaintenanceDone chan bool\n\tbgWg            sync.WaitGroup\n\n\t// TODO initialize via reflection; convert comments to tag 'description'.\n\tmetrics struct {\n\t\t// ValueLogSpaceAvailable returns the amount of space left on the value log mount point in bytes\n\t\tValueLogSpaceAvailable metrics.Gauge\n\t\t// KeyLogSpaceAvailable returns the amount of space left on the key log mount point in bytes\n\t\tKeyLogSpaceAvailable metrics.Gauge\n\t\t// LastMaintenanceRun stores the timestamp (UnixNano) of the previous maintenanceRun\n\t\tLastMaintenanceRun metrics.Gauge\n\t\t// LastValueLogCleaned stores the timestamp (UnixNano) of the previous ValueLogGC run\n\t\tLastValueLogCleaned metrics.Gauge\n\n\t\t// Expose badger's internal expvar metrics, which are all gauge's at this point\n\t\tbadgerMetrics map[string]metrics.Gauge\n\t}\n}\n\n// NewFactory creates a new Factory.\nfunc NewFactory() *Factory {\n\treturn &Factory{\n\t\tConfig:          DefaultConfig(),\n\t\tmaintenanceDone: make(chan bool),\n\t}\n}\n\n// Initialize performs internal initialization of the factory.\nfunc (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) error {\n\tf.logger = logger\n\tf.metricsFactory = metricsFactory\n\n\topts := badger.DefaultOptions(\"\")\n\n\tif f.Config.Ephemeral {\n\t\topts.SyncWrites = false\n\t\t// Error from TempDir is ignored to satisfy Codecov\n\t\tdir, _ := os.MkdirTemp(\"\", \"badger\")\n\t\tf.tmpDir = dir\n\t\topts.Dir = f.tmpDir\n\t\topts.ValueDir = f.tmpDir\n\n\t\tf.Config.Directories.Keys = f.tmpDir\n\t\tf.Config.Directories.Values = f.tmpDir\n\t} else {\n\t\t// Errors are ignored as they're caught in the Open call\n\t\tinitializeDir(f.Config.Directories.Keys)\n\t\tinitializeDir(f.Config.Directories.Values)\n\n\t\topts.SyncWrites = f.Config.SyncWrites\n\t\topts.Dir = f.Config.Directories.Keys\n\t\topts.ValueDir = f.Config.Directories.Values\n\n\t\t// These options make no sense with ephemeral data\n\t\topts.ReadOnly = f.Config.ReadOnly\n\t}\n\n\tstore, err := badger.Open(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tf.store = store\n\n\tf.cache = badgerstore.NewCacheStore(f.store, f.Config.TTL.Spans)\n\n\tf.metrics.ValueLogSpaceAvailable = metricsFactory.Gauge(metrics.Options{Name: valueLogSpaceAvailableName})\n\tf.metrics.KeyLogSpaceAvailable = metricsFactory.Gauge(metrics.Options{Name: keyLogSpaceAvailableName})\n\tf.metrics.LastMaintenanceRun = metricsFactory.Gauge(metrics.Options{Name: lastMaintenanceRunName})\n\tf.metrics.LastValueLogCleaned = metricsFactory.Gauge(metrics.Options{Name: lastValueLogCleanedName})\n\n\tf.registerBadgerExpvarMetrics(metricsFactory)\n\n\tf.bgWg.Add(2)\n\tgo func() {\n\t\tdefer f.bgWg.Done()\n\t\tf.maintenance()\n\t}()\n\tgo func() {\n\t\tdefer f.bgWg.Done()\n\t\tf.metricsCopier()\n\t}()\n\n\tlogger.Info(\"Badger storage configuration\", zap.Any(\"configuration\", opts))\n\n\treturn nil\n}\n\n// initializeDir makes the directory and parent directories if the path doesn't exists yet.\nfunc initializeDir(path string) {\n\tif _, err := os.Stat(path); err != nil && os.IsNotExist(err) {\n\t\tos.MkdirAll(path, 0o700)\n\t}\n}\n\n// CreateSpanReader creates a spanstore.Reader.\nfunc (f *Factory) CreateSpanReader() (spanstore.Reader, error) {\n\ttr := badgerstore.NewTraceReader(f.store, f.cache, true)\n\treturn spanstoremetrics.NewReaderDecorator(tr, f.metricsFactory), nil\n}\n\n// CreateSpanWriter creates a spanstore.Writer.\nfunc (f *Factory) CreateSpanWriter() (spanstore.Writer, error) {\n\treturn badgerstore.NewSpanWriter(f.store, f.cache, f.Config.TTL.Spans), nil\n}\n\n// CreateDependencyReader creates a dependencystore.Reader.\nfunc (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) {\n\tsr, _ := f.CreateSpanReader() // err is always nil\n\treturn depstore.NewDependencyStore(sr), nil\n}\n\n// CreateSamplingStore implements storage.SamplingStoreFactory\nfunc (f *Factory) CreateSamplingStore(int /* maxBuckets */) (samplingstore.Store, error) {\n\treturn badgersampling.NewSamplingStore(f.store), nil\n}\n\n// CreateLock implements storage.SamplingStoreFactory\nfunc (*Factory) CreateLock() (distributedlock.Lock, error) {\n\treturn &lock{}, nil\n}\n\n// Close Implements io.Closer and closes the underlying storage\nfunc (f *Factory) Close() error {\n\tclose(f.maintenanceDone)\n\tf.bgWg.Wait() // Wait for background goroutines to finish before closing store\n\tif f.store == nil {\n\t\treturn nil\n\t}\n\terr := f.store.Close()\n\n\t// Remove tmp files if this was ephemeral storage\n\tif f.Config.Ephemeral {\n\t\terrSecondary := os.RemoveAll(f.tmpDir)\n\t\tif err == nil {\n\t\t\terr = errSecondary\n\t\t}\n\t}\n\n\treturn err\n}\n\n// Maintenance starts a background maintenance job for the badger K/V store, such as ValueLogGC\nfunc (f *Factory) maintenance() {\n\tmaintenanceTicker := time.NewTicker(f.Config.MaintenanceInterval)\n\tdefer maintenanceTicker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-f.maintenanceDone:\n\t\t\treturn\n\t\tcase t := <-maintenanceTicker.C:\n\t\t\tvar err error\n\n\t\t\t// After there's nothing to clean, the err is raised\n\t\t\tfor err == nil {\n\t\t\t\terr = f.store.RunValueLogGC(0.5) // 0.5 is selected to rewrite a file if half of it can be discarded\n\t\t\t}\n\t\t\tif errors.Is(err, badger.ErrNoRewrite) {\n\t\t\t\tf.metrics.LastValueLogCleaned.Update(t.UnixNano())\n\t\t\t} else {\n\t\t\t\tf.logger.Error(\"Failed to run ValueLogGC\", zap.Error(err))\n\t\t\t}\n\n\t\t\tf.metrics.LastMaintenanceRun.Update(t.UnixNano())\n\t\t\t_ = f.diskStatisticsUpdate()\n\t\t}\n\t}\n}\n\nfunc (f *Factory) metricsCopier() {\n\tmetricsTicker := time.NewTicker(f.Config.MetricsUpdateInterval)\n\tdefer metricsTicker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-f.maintenanceDone:\n\t\t\treturn\n\t\tcase <-metricsTicker.C:\n\t\t\texpvar.Do(func(kv expvar.KeyValue) {\n\t\t\t\tif strings.HasPrefix(kv.Key, \"badger\") {\n\t\t\t\t\tswitch val := kv.Value.(type) {\n\t\t\t\t\tcase *expvar.Int:\n\t\t\t\t\t\tif g, found := f.metrics.badgerMetrics[kv.Key]; found {\n\t\t\t\t\t\t\tg.Update(val.Value())\n\t\t\t\t\t\t}\n\t\t\t\t\tcase *expvar.Map:\n\t\t\t\t\t\tval.Do(func(innerKv expvar.KeyValue) {\n\t\t\t\t\t\t\t// The metrics we're interested in have only a single inner key (dynamic name)\n\t\t\t\t\t\t\t// and we're only interested in its value\n\t\t\t\t\t\t\tif intVal, ok := innerKv.Value.(*expvar.Int); ok {\n\t\t\t\t\t\t\t\tif g, found := f.metrics.badgerMetrics[kv.Key]; found {\n\t\t\t\t\t\t\t\t\tg.Update(intVal.Value())\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tf.logger.Debug(\"skipping non-numeric badger expvar metric\", zap.String(\"key\", kv.Key))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc (f *Factory) registerBadgerExpvarMetrics(metricsFactory metrics.Factory) {\n\tf.metrics.badgerMetrics = make(map[string]metrics.Gauge)\n\n\texpvar.Do(func(kv expvar.KeyValue) {\n\t\tif strings.HasPrefix(kv.Key, \"badger\") {\n\t\t\tswitch val := kv.Value.(type) {\n\t\t\tcase *expvar.Int:\n\t\t\t\tg := metricsFactory.Gauge(metrics.Options{Name: kv.Key})\n\t\t\t\tf.metrics.badgerMetrics[kv.Key] = g\n\t\t\tcase *expvar.Map:\n\t\t\t\tval.Do(func(innerKv expvar.KeyValue) {\n\t\t\t\t\t// The metrics we're interested in have only a single inner key (dynamic name)\n\t\t\t\t\t// and we're only interested in its value\n\t\t\t\t\tif _, ok := innerKv.Value.(*expvar.Int); ok {\n\t\t\t\t\t\tg := metricsFactory.Gauge(metrics.Options{Name: kv.Key})\n\t\t\t\t\t\tf.metrics.badgerMetrics[kv.Key] = g\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tdefault:\n\t\t\t\tf.logger.Info(\"skipping non-numeric badger expvar metric\", zap.String(\"key\", kv.Key))\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Purge removes all data from the Factory's underlying Badger store.\n// This function is intended for testing purposes only and should not be used in production environments.\n// Calling Purge in production will result in permanent data loss.\nfunc (f *Factory) Purge(_ context.Context) error {\n\treturn f.store.Update(func(_ *badger.Txn) error {\n\t\treturn f.store.DropAll()\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/factory_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"expvar\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n)\n\nfunc TestInitializationErrors(t *testing.T) {\n\tf := NewFactory()\n\tdir := \"/root/this_should_fail\" // If this test fails, you have some issues in your system\n\tf.Config.Ephemeral = false\n\tf.Config.SyncWrites = true\n\tf.Config.Directories.Keys = dir\n\tf.Config.Directories.Values = dir\n\n\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\trequire.Error(t, err)\n}\n\nfunc TestForCodecov(t *testing.T) {\n\t// These tests are testing our vendor packages and are intended to satisfy Codecov.\n\tf := NewFactory()\n\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\n\t// Get all the writers, readers, etc\n\t_, err = f.CreateSpanReader()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateSpanWriter()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateDependencyReader()\n\trequire.NoError(t, err)\n\n\tlock, err := f.CreateLock()\n\trequire.NoError(t, err)\n\tassert.NotNil(t, lock)\n\n\t// Now, remove the badger directories\n\terr = os.RemoveAll(f.tmpDir)\n\trequire.NoError(t, err)\n\n\t// Now try to close, since the files have been deleted this should throw an error\n\terr = f.Close()\n\trequire.Error(t, err)\n}\n\nfunc TestMaintenanceRun(t *testing.T) {\n\t// For Codecov - this does not test anything\n\tf := NewFactory()\n\tf.Config.MaintenanceInterval = 10 * time.Millisecond\n\t// Safeguard\n\tmFactory := metricstest.NewFactory(0)\n\t_, gs := mFactory.Snapshot()\n\tassert.Equal(t, int64(0), gs[lastMaintenanceRunName])\n\terr := f.Initialize(mFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\n\twaiter := func(previousValue int64) int64 {\n\t\tsleeps := 0\n\t\t_, gs := mFactory.Snapshot()\n\t\tfor gs[lastMaintenanceRunName] == previousValue && sleeps < 8 {\n\t\t\t// Wait for the scheduler\n\t\t\ttime.Sleep(time.Duration(50) * time.Millisecond)\n\t\t\tsleeps++\n\t\t\t_, gs = mFactory.Snapshot()\n\t\t}\n\t\tassert.Greater(t, gs[lastMaintenanceRunName], previousValue)\n\t\treturn gs[lastMaintenanceRunName]\n\t}\n\n\truntime := waiter(0) // First run, check that it was ran and caches previous size\n\n\t// This is to for codecov only. Can break without anything else breaking as it does test badger's\n\t// internal implementation\n\tvlogSize := expvar.Get(\"badger_size_bytes_vlog\").(*expvar.Map).Get(f.tmpDir).(*expvar.Int)\n\tcurrSize := vlogSize.Value()\n\tvlogSize.Set(currSize + 1<<31)\n\n\twaiter(runtime)\n\t_, gs = mFactory.Snapshot()\n\tassert.Positive(t, gs[lastValueLogCleanedName])\n}\n\n// TestMaintenanceCodecov this test is not intended to test anything, but hopefully increase coverage by triggering a log line\nfunc TestMaintenanceCodecov(t *testing.T) {\n\t// For Codecov - this does not test anything\n\tf := NewFactory()\n\tf.Config.MaintenanceInterval = 10 * time.Millisecond\n\tmFactory := metricstest.NewFactory(0)\n\terr := f.Initialize(mFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\n\twaiter := func() {\n\t\tfor range 8 {\n\t\t\t// Wait for the scheduler\n\t\t\ttime.Sleep(time.Duration(50) * time.Millisecond)\n\t\t}\n\t}\n\n\terr = f.store.Close()\n\trequire.NoError(t, err)\n\twaiter() // This should trigger the logging of error\n}\n\nfunc TestBadgerMetrics(t *testing.T) {\n\t// The expvar is leaking keyparams between tests. We need to clean up a bit..\n\teMap := expvar.Get(\"badger_size_bytes_lsm\").(*expvar.Map)\n\teMap.Init()\n\n\tf := NewFactory()\n\tf.Config.MetricsUpdateInterval = 10 * time.Millisecond\n\tmFactory := metricstest.NewFactory(0)\n\terr := f.Initialize(mFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\tassert.NotNil(t, f.metrics.badgerMetrics)\n\t_, found := f.metrics.badgerMetrics[\"badger_get_num_memtable\"]\n\tassert.True(t, found)\n\n\twaiter := func(previousValue int64) int64 {\n\t\tsleeps := 0\n\t\t_, gs := mFactory.Snapshot()\n\t\tfor gs[\"badger_get_num_memtable\"] == previousValue && sleeps < 8 {\n\t\t\t// Wait for the scheduler\n\t\t\ttime.Sleep(time.Duration(50) * time.Millisecond)\n\t\t\tsleeps++\n\t\t\t_, gs = mFactory.Snapshot()\n\t\t}\n\t\tassert.Equal(t, gs[\"badger_get_num_memtable\"], previousValue)\n\t\treturn gs[\"badger_get_num_memtable\"]\n\t}\n\n\tvlogSize := waiter(0)\n\t_, gs := mFactory.Snapshot()\n\tassert.EqualValues(t, 0, vlogSize)\n\tassert.Equal(t, int64(0), gs[\"badger_get_num_memtable\"]) // IntVal metric\n\n\t_, found = gs[\"badger_size_bytes_lsm\"] // Map metric\n\tassert.True(t, found)\n\n\trequire.NoError(t, f.Close())\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/lock.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport \"time\"\n\ntype lock struct{}\n\n// Acquire always returns true for badgerdb as no lock is needed\nfunc (*lock) Acquire(string /* resource */, time.Duration /* ttl */) (bool, error) {\n\treturn true, nil\n}\n\n// Forfeit always returns true for badgerdb as no lock is needed\nfunc (*lock) Forfeit(string /* resource */) (bool, error) {\n\treturn true, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/lock_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAcquire(t *testing.T) {\n\tl := &lock{}\n\tok, err := l.Acquire(\"resource\", time.Duration(1))\n\tassert.True(t, ok)\n\trequire.NoError(t, err)\n}\n\nfunc TestForfeit(t *testing.T) {\n\tl := &lock{}\n\tok, err := l.Forfeit(\"resource\")\n\tassert.True(t, ok)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/options.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n)\n\nconst (\n\tprefix                    = \"badger\"\n\tsuffixKeyDirectory        = \".directory-key\"\n\tsuffixValueDirectory      = \".directory-value\"\n\tsuffixEphemeral           = \".ephemeral\"\n\tsuffixSpanstoreTTL        = \".span-store-ttl\"\n\tsuffixSyncWrite           = \".consistency\"\n\tsuffixMaintenanceInterval = \".maintenance-interval\"\n\tsuffixMetricsInterval     = \".metrics-update-interval\" // Intended only for testing purposes\n\tsuffixReadOnly            = \".read-only\"\n)\n\n// AddFlags adds flags for Config.\nfunc (c *Config) AddFlags(flagSet *flag.FlagSet) {\n\tflagSet.Bool(\n\t\tprefix+suffixEphemeral,\n\t\tc.Ephemeral,\n\t\t\"Mark this storage ephemeral, data is stored in tmpfs.\",\n\t)\n\tflagSet.Duration(\n\t\tprefix+suffixSpanstoreTTL,\n\t\tc.TTL.Spans,\n\t\t\"How long to store the data. Format is time.Duration (https://golang.org/pkg/time/#Duration)\",\n\t)\n\tflagSet.String(\n\t\tprefix+suffixKeyDirectory,\n\t\tc.Directories.Keys,\n\t\t\"Path to store the keys (indexes), this directory should reside in SSD disk. Set ephemeral to false if you want to define this setting.\",\n\t)\n\tflagSet.String(\n\t\tprefix+suffixValueDirectory,\n\t\tc.Directories.Values,\n\t\t\"Path to store the values (spans). Set ephemeral to false if you want to define this setting.\",\n\t)\n\tflagSet.Bool(\n\t\tprefix+suffixSyncWrite,\n\t\tc.SyncWrites,\n\t\t\"If all writes should be synced immediately to physical disk. This will impact write performance.\",\n\t)\n\tflagSet.Duration(\n\t\tprefix+suffixMaintenanceInterval,\n\t\tc.MaintenanceInterval,\n\t\t\"How often the maintenance thread for values is ran. Format is time.Duration (https://golang.org/pkg/time/#Duration)\",\n\t)\n\tflagSet.Duration(\n\t\tprefix+suffixMetricsInterval,\n\t\tc.MetricsUpdateInterval,\n\t\t\"How often the badger metrics are collected by Jaeger. Format is time.Duration (https://golang.org/pkg/time/#Duration)\",\n\t)\n\tflagSet.Bool(\n\t\tprefix+suffixReadOnly,\n\t\tc.ReadOnly,\n\t\t\"Allows to open badger database in read only mode. Multiple instances can open same database in read-only mode. Values still in the write-ahead-log must be replayed before opening.\",\n\t)\n}\n\n// InitFromViper initializes Config with properties from viper.\nfunc (c *Config) InitFromViper(v *viper.Viper, logger *zap.Logger) {\n\tinitFromViper(c, v, logger)\n}\n\nfunc initFromViper(config *Config, v *viper.Viper, _ *zap.Logger) {\n\tconfig.Ephemeral = v.GetBool(prefix + suffixEphemeral)\n\tconfig.Directories.Keys = v.GetString(prefix + suffixKeyDirectory)\n\tconfig.Directories.Values = v.GetString(prefix + suffixValueDirectory)\n\tconfig.SyncWrites = v.GetBool(prefix + suffixSyncWrite)\n\tconfig.TTL.Spans = v.GetDuration(prefix + suffixSpanstoreTTL)\n\tconfig.MaintenanceInterval = v.GetDuration(prefix + suffixMaintenanceInterval)\n\tconfig.MetricsUpdateInterval = v.GetDuration(prefix + suffixMetricsInterval)\n\tconfig.ReadOnly = v.GetBool(prefix + suffixReadOnly)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/options_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDefaultConfigParsing(t *testing.T) {\n\tcfg := DefaultConfig()\n\n\tassert.True(t, cfg.Ephemeral)\n\tassert.False(t, cfg.SyncWrites)\n\tassert.Equal(t, time.Duration(72*time.Hour), cfg.TTL.Spans)\n}\n\nfunc TestParseConfig(t *testing.T) {\n\tcfg := &Config{\n\t\tEphemeral:  false,\n\t\tSyncWrites: true,\n\t\tTTL: TTL{\n\t\t\tSpans: 168 * time.Hour,\n\t\t},\n\t\tDirectories: Directories{\n\t\t\tKeys:   \"/var/lib/badger\",\n\t\t\tValues: \"/mnt/slow/badger\",\n\t\t},\n\t\tReadOnly: false,\n\t}\n\n\tassert.False(t, cfg.Ephemeral)\n\tassert.True(t, cfg.SyncWrites)\n\tassert.Equal(t, time.Duration(168*time.Hour), cfg.TTL.Spans)\n\tassert.Equal(t, \"/var/lib/badger\", cfg.Directories.Keys)\n\tassert.Equal(t, \"/mnt/slow/badger\", cfg.Directories.Values)\n\tassert.False(t, cfg.ReadOnly)\n}\n\nfunc TestReadOnlyConfig(t *testing.T) {\n\tcfg := DefaultConfig()\n\tcfg.ReadOnly = true\n\tassert.True(t, cfg.ReadOnly)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/samplingstore/storage.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\n\tjaegermodel \"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nconst (\n\tthroughputKeyPrefix    byte = 0x08\n\tprobabilitiesKeyPrefix byte = 0x09\n)\n\ntype SamplingStore struct {\n\tstore *badger.DB\n}\n\ntype ProbabilitiesAndQPS struct {\n\tHostname      string\n\tProbabilities model.ServiceOperationProbabilities\n\tQPS           model.ServiceOperationQPS\n}\n\nfunc NewSamplingStore(db *badger.DB) *SamplingStore {\n\treturn &SamplingStore{\n\t\tstore: db,\n\t}\n}\n\nfunc (s *SamplingStore) InsertThroughput(throughput []*model.Throughput) error {\n\tstartTime := jaegermodel.TimeAsEpochMicroseconds(time.Now())\n\tentriesToStore := make([]*badger.Entry, 0)\n\tentries, err := s.createThroughputEntry(throughput, startTime)\n\tif err != nil {\n\t\treturn err\n\t}\n\tentriesToStore = append(entriesToStore, entries)\n\terr = s.store.Update(func(txn *badger.Txn) error {\n\t\tfor i := range entriesToStore {\n\t\t\terr = txn.SetEntry(entriesToStore[i])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n\nfunc (s *SamplingStore) GetThroughput(start, end time.Time) ([]*model.Throughput, error) {\n\tvar retSlice []*model.Throughput\n\tprefix := []byte{throughputKeyPrefix}\n\n\terr := s.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\tval := []byte{}\n\t\tfor it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {\n\t\t\titem := it.Item()\n\t\t\tk := item.Key()\n\t\t\tstartTime := k[1:9]\n\t\t\tval, err := item.ValueCopy(val)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tt, err := initalStartTime(startTime)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tthroughputs, err := decodeThroughputValue(val)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif t.After(start) && (t.Before(end) || t.Equal(end)) {\n\t\t\t\tretSlice = append(retSlice, throughputs...)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn retSlice, nil\n}\n\nfunc (s *SamplingStore) InsertProbabilitiesAndQPS(hostname string,\n\tprobabilities model.ServiceOperationProbabilities,\n\tqps model.ServiceOperationQPS,\n) error {\n\tstartTime := jaegermodel.TimeAsEpochMicroseconds(time.Now())\n\tentriesToStore := make([]*badger.Entry, 0)\n\tentries, err := s.createProbabilitiesEntry(hostname, probabilities, qps, startTime)\n\tif err != nil {\n\t\treturn err\n\t}\n\tentriesToStore = append(entriesToStore, entries)\n\terr = s.store.Update(func(txn *badger.Txn) error {\n\t\t// Write the entries\n\t\tfor i := range entriesToStore {\n\t\t\terr = txn.SetEntry(entriesToStore[i])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn nil\n}\n\n// GetLatestProbabilities implements samplingstore.Reader#GetLatestProbabilities.\nfunc (s *SamplingStore) GetLatestProbabilities() (model.ServiceOperationProbabilities, error) {\n\tvar retVal model.ServiceOperationProbabilities\n\tvar unMarshalProbabilities ProbabilitiesAndQPS\n\tprefix := []byte{probabilitiesKeyPrefix}\n\n\terr := s.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\tval := []byte{}\n\t\tfor it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {\n\t\t\titem := it.Item()\n\t\t\tval, err := item.ValueCopy(val)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tunMarshalProbabilities, err = decodeProbabilitiesValue(val)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tretVal = unMarshalProbabilities.Probabilities\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn retVal, nil\n}\n\nfunc (s *SamplingStore) createProbabilitiesEntry(hostname string, probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS, startTime uint64) (*badger.Entry, error) {\n\tpK, pV, err := s.createProbabilitiesKV(hostname, probabilities, qps, startTime)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := s.createBadgerEntry(pK, pV)\n\n\treturn e, nil\n}\n\nfunc (*SamplingStore) createProbabilitiesKV(hostname string, probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS, startTime uint64) (key []byte, bb []byte, err error) {\n\tkey = make([]byte, 16)\n\tkey[0] = probabilitiesKeyPrefix\n\tpos := 1\n\tbinary.BigEndian.PutUint64(key[pos:], startTime)\n\n\tval := ProbabilitiesAndQPS{\n\t\tHostname:      hostname,\n\t\tProbabilities: probabilities,\n\t\tQPS:           qps,\n\t}\n\tbb, err = json.Marshal(val)\n\treturn key, bb, err\n}\n\nfunc (s *SamplingStore) createThroughputEntry(throughput []*model.Throughput, startTime uint64) (*badger.Entry, error) {\n\tpK, pV, err := s.createThroughputKV(throughput, startTime)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := s.createBadgerEntry(pK, pV)\n\n\treturn e, nil\n}\n\nfunc (*SamplingStore) createBadgerEntry(key []byte, value []byte) *badger.Entry {\n\treturn &badger.Entry{\n\t\tKey:   key,\n\t\tValue: value,\n\t}\n}\n\nfunc (*SamplingStore) createThroughputKV(throughput []*model.Throughput, startTime uint64) (key []byte, bb []byte, err error) {\n\tkey = make([]byte, 16)\n\tkey[0] = throughputKeyPrefix\n\tpos := 1\n\tbinary.BigEndian.PutUint64(key[pos:], startTime)\n\n\tbb, err = json.Marshal(throughput)\n\treturn key, bb, err\n}\n\nfunc decodeThroughputValue(val []byte) ([]*model.Throughput, error) {\n\tvar throughput []*model.Throughput\n\n\terr := json.Unmarshal(val, &throughput)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn throughput, err\n}\n\nfunc decodeProbabilitiesValue(val []byte) (ProbabilitiesAndQPS, error) {\n\tvar probabilities ProbabilitiesAndQPS\n\n\terr := json.Unmarshal(val, &probabilities)\n\tif err != nil {\n\t\treturn ProbabilitiesAndQPS{}, err\n\t}\n\treturn probabilities, nil\n}\n\nfunc initalStartTime(timeBytes []byte) (time.Time, error) {\n\tvar usec int64\n\n\tbuf := bytes.NewReader(timeBytes)\n\n\tif err := binary.Read(buf, binary.BigEndian, &usec); err != nil {\n\t\treturn time.Time{}, err\n\t}\n\n\tt := time.UnixMicro(usec)\n\treturn t, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/samplingstore/storage_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tsamplemodel \"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc newTestSamplingStore(db *badger.DB) *SamplingStore {\n\treturn NewSamplingStore(db)\n}\n\nfunc TestInsertThroughput(t *testing.T) {\n\trunWithBadger(t, func(t *testing.T, store *SamplingStore) {\n\t\tthroughputs := []*samplemodel.Throughput{\n\t\t\t{Service: \"my-svc\", Operation: \"op\"},\n\t\t\t{Service: \"our-svc\", Operation: \"op2\"},\n\t\t}\n\t\terr := store.InsertThroughput(throughputs)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestGetThroughput(t *testing.T) {\n\trunWithBadger(t, func(t *testing.T, store *SamplingStore) {\n\t\tstart := time.Now()\n\t\texpected := []*samplemodel.Throughput{\n\t\t\t{Service: \"my-svc\", Operation: \"op\"},\n\t\t\t{Service: \"our-svc\", Operation: \"op2\"},\n\t\t}\n\t\terr := store.InsertThroughput(expected)\n\t\trequire.NoError(t, err)\n\n\t\tactual, err := store.GetThroughput(start.Add(-time.Millisecond), start.Add(time.Second*time.Duration(10)))\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expected, actual)\n\t})\n}\n\nfunc TestInsertProbabilitiesAndQPS(t *testing.T) {\n\trunWithBadger(t, func(t *testing.T, store *SamplingStore) {\n\t\terr := store.InsertProbabilitiesAndQPS(\n\t\t\t\"dell11eg843d\",\n\t\t\tsamplemodel.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}},\n\t\t\tsamplemodel.ServiceOperationQPS{\"new-srv\": {\"op\": 4}},\n\t\t)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestGetLatestProbabilities(t *testing.T) {\n\trunWithBadger(t, func(t *testing.T, store *SamplingStore) {\n\t\terr := store.InsertProbabilitiesAndQPS(\n\t\t\t\"dell11eg843d\",\n\t\t\tsamplemodel.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}},\n\t\t\tsamplemodel.ServiceOperationQPS{\"new-srv\": {\"op\": 4}},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\terr = store.InsertProbabilitiesAndQPS(\n\t\t\t\"newhostname\",\n\t\t\tsamplemodel.ServiceOperationProbabilities{\"new-srv2\": {\"op\": 0.123}},\n\t\t\tsamplemodel.ServiceOperationQPS{\"new-srv2\": {\"op\": 1}},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\texpected := samplemodel.ServiceOperationProbabilities{\"new-srv2\": {\"op\": 0.123}}\n\t\tactual, err := store.GetLatestProbabilities()\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expected, actual)\n\t})\n}\n\nfunc TestDecodeProbabilitiesValue(t *testing.T) {\n\texpected := ProbabilitiesAndQPS{\n\t\tHostname:      \"dell11eg843d\",\n\t\tProbabilities: samplemodel.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}},\n\t\tQPS:           samplemodel.ServiceOperationQPS{\"new-srv\": {\"op\": 4}},\n\t}\n\n\tmarshalBytes, err := json.Marshal(expected)\n\trequire.NoError(t, err)\n\t// This should pass without error\n\tactual, err := decodeProbabilitiesValue(marshalBytes)\n\trequire.NoError(t, err)\n\tassert.Equal(t, expected, actual)\n\n\t// Simulate data corruption by removing the first byte.\n\tcorruptedBytes := marshalBytes[1:]\n\t_, err = decodeProbabilitiesValue(corruptedBytes)\n\trequire.Error(t, err) // Expect an error\n}\n\nfunc TestDecodeThroughtputValue(t *testing.T) {\n\texpected := []*samplemodel.Throughput{\n\t\t{Service: \"my-svc\", Operation: \"op\"},\n\t\t{Service: \"our-svc\", Operation: \"op2\"},\n\t}\n\n\tmarshalBytes, err := json.Marshal(expected)\n\trequire.NoError(t, err)\n\tacrual, err := decodeThroughputValue(marshalBytes)\n\trequire.NoError(t, err)\n\tassert.Equal(t, expected, acrual)\n}\n\nfunc runWithBadger(t *testing.T, test func(t *testing.T, store *SamplingStore)) {\n\topts := badger.DefaultOptions(\"\")\n\n\topts.SyncWrites = false\n\tdir := t.TempDir()\n\topts.Dir = dir\n\topts.ValueDir = dir\n\n\tstore, err := badger.Open(opts)\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\trequire.NoError(t, store.Close())\n\t}()\n\tss := newTestSamplingStore(store)\n\ttest(t, ss)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/cache.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// CacheStore saves expensive calculations from the K/V store\ntype CacheStore struct {\n\t// Given the small amount of data these will store, we use the same structure as the memory store\n\tcacheLock  sync.Mutex // write heavy - Mutex is faster than RWMutex for writes\n\tservices   map[string]uint64\n\toperations map[string]map[string]uint64\n\n\tstore *badger.DB\n\tttl   time.Duration\n}\n\n// NewCacheStore returns initialized CacheStore for badger use\nfunc NewCacheStore(db *badger.DB, ttl time.Duration) *CacheStore {\n\tcs := &CacheStore{\n\t\tservices:   make(map[string]uint64),\n\t\toperations: make(map[string]map[string]uint64),\n\t\tttl:        ttl,\n\t\tstore:      db,\n\t}\n\treturn cs\n}\n\n// AddService fills the services into the cache with the most updated expiration time\nfunc (c *CacheStore) AddService(service string, keyTTL uint64) {\n\tc.cacheLock.Lock()\n\tdefer c.cacheLock.Unlock()\n\tif v, found := c.services[service]; found {\n\t\tif v > keyTTL {\n\t\t\treturn\n\t\t}\n\t}\n\tc.services[service] = keyTTL\n}\n\n// AddOperation adds the cache with operation names with most updated expiration time\nfunc (c *CacheStore) AddOperation(service, operation string, keyTTL uint64) {\n\tc.cacheLock.Lock()\n\tdefer c.cacheLock.Unlock()\n\tif _, found := c.operations[service]; !found {\n\t\tc.operations[service] = make(map[string]uint64)\n\t}\n\tif v, found := c.operations[service][operation]; found {\n\t\tif v > keyTTL {\n\t\t\treturn\n\t\t}\n\t}\n\tc.operations[service][operation] = keyTTL\n}\n\n// Update caches the results of service and service + operation indexes and maintains their TTL\nfunc (c *CacheStore) Update(service, operation string, expireTime uint64) {\n\tc.cacheLock.Lock()\n\n\tc.services[service] = expireTime\n\tif _, ok := c.operations[service]; !ok {\n\t\tc.operations[service] = make(map[string]uint64)\n\t}\n\tc.operations[service][operation] = expireTime\n\tc.cacheLock.Unlock()\n}\n\n// GetOperations returns all operations for a specific service & spanKind traced by Jaeger\nfunc (c *CacheStore) GetOperations(service string) ([]spanstore.Operation, error) {\n\toperations := make([]string, 0, len(c.services))\n\t//nolint:gosec // G115\n\tt := uint64(time.Now().Unix())\n\tc.cacheLock.Lock()\n\tdefer c.cacheLock.Unlock()\n\n\tif v, ok := c.services[service]; ok {\n\t\tif v < t {\n\t\t\t// Expired, remove\n\t\t\tdelete(c.services, service)\n\t\t\tdelete(c.operations, service)\n\t\t\treturn []spanstore.Operation{}, nil // empty slice rather than nil\n\t\t}\n\t\tfor o, e := range c.operations[service] {\n\t\t\tif e > t {\n\t\t\t\toperations = append(operations, o)\n\t\t\t} else {\n\t\t\t\tdelete(c.operations[service], o)\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Strings(operations)\n\n\t// TODO: https://github.com/jaegertracing/jaeger/issues/1922\n\t// \t- return the operations with actual spanKind\n\tresult := make([]spanstore.Operation, 0, len(operations))\n\tfor _, op := range operations {\n\t\tresult = append(result, spanstore.Operation{\n\t\t\tName: op,\n\t\t})\n\t}\n\treturn result, nil\n}\n\n// GetServices returns all services traced by Jaeger\nfunc (c *CacheStore) GetServices() ([]string, error) {\n\tservices := make([]string, 0, len(c.services))\n\t//nolint:gosec // G115\n\tt := uint64(time.Now().Unix())\n\tc.cacheLock.Lock()\n\t// Fetch the items\n\tfor k, v := range c.services {\n\t\tif v > t {\n\t\t\tservices = append(services, k)\n\t\t} else {\n\t\t\t// Service has expired, remove it\n\t\t\tdelete(c.services, k)\n\t\t}\n\t}\n\tc.cacheLock.Unlock()\n\n\tsort.Strings(services)\n\n\treturn services, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/cache_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n/*\n\tAdditional cache store tests that need to access internal parts. As such, package must be spanstore and not spanstore_test\n*/\n\nfunc TestExpiredItems(t *testing.T) {\n\trunWithBadger(t, func(store *badger.DB, t *testing.T) {\n\t\tcache := NewCacheStore(store, time.Duration(-1*time.Hour))\n\n\t\texpireTime := uint64(time.Now().Add(cache.ttl).Unix())\n\n\t\t// Expired service\n\n\t\tcache.Update(\"service1\", \"op1\", expireTime)\n\t\tcache.Update(\"service1\", \"op2\", expireTime)\n\n\t\tservices, err := cache.GetServices()\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, services) // Everything should be expired\n\n\t\t// Expired service for operations\n\n\t\tcache.Update(\"service1\", \"op1\", expireTime)\n\t\tcache.Update(\"service1\", \"op2\", expireTime)\n\n\t\toperations, err := cache.GetOperations(\"service1\")\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, operations) // Everything should be expired\n\n\t\t// Expired operations, stable service\n\n\t\tcache.Update(\"service1\", \"op1\", expireTime)\n\t\tcache.Update(\"service1\", \"op2\", expireTime)\n\n\t\tcache.services[\"service1\"] = uint64(time.Now().Unix() + 1e10)\n\n\t\toperations, err = cache.GetOperations(\"service1\")\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, operations) // Everything should be expired\n\t})\n}\n\n// func runFactoryTest(tb testing.TB, test func(tb testing.TB, sw spanstore.Writer, sr spanstore.Reader)) {\nfunc runWithBadger(t *testing.T, test func(store *badger.DB, t *testing.T)) {\n\topts := badger.DefaultOptions(\"\")\n\n\topts.SyncWrites = false\n\tdir := t.TempDir()\n\topts.Dir = dir\n\topts.ValueDir = dir\n\n\tstore, err := badger.Open(opts)\n\tdefer func() {\n\t\tstore.Close()\n\t}()\n\n\trequire.NoError(t, err)\n\n\ttest(store, t)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/read_write_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime/pprof\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n)\n\nfunc TestWriteReadBack(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, sw spanstore.Writer, sr spanstore.Reader) {\n\t\ttid := time.Now()\n\t\ttraces := 40\n\t\tspans := 3\n\n\t\tdummyKv := []model.KeyValue{\n\t\t\t{\n\t\t\t\tKey:   \"key\",\n\t\t\t\tVType: model.StringType,\n\t\t\t\tVStr:  \"value\",\n\t\t\t},\n\t\t}\n\n\t\tfor i := range traces {\n\t\t\tfor j := range spans {\n\t\t\t\ts := model.Span{\n\t\t\t\t\tTraceID: model.TraceID{\n\t\t\t\t\t\tLow:  uint64(i),\n\t\t\t\t\t\tHigh: 1,\n\t\t\t\t\t},\n\t\t\t\t\tSpanID:        model.SpanID(j),\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t\tProcess: &model.Process{\n\t\t\t\t\t\tServiceName: \"service\",\n\t\t\t\t\t\tTags:        dummyKv,\n\t\t\t\t\t},\n\t\t\t\t\tStartTime: tid.Add(time.Duration(i)),\n\t\t\t\t\tDuration:  time.Duration(i + j),\n\t\t\t\t\tTags:      dummyKv,\n\t\t\t\t\tLogs: []model.Log{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tTimestamp: tid,\n\t\t\t\t\t\t\tFields:    dummyKv,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\terr := sw.WriteSpan(context.Background(), &s)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\n\t\tfor i := range traces {\n\t\t\ttr, err := sr.GetTrace(context.Background(), spanstore.GetTraceParameters{TraceID: model.TraceID{\n\t\t\t\tLow:  uint64(i),\n\t\t\t\tHigh: 1,\n\t\t\t}})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tassert.Len(t, tr.Spans, spans)\n\t\t}\n\t})\n}\n\nfunc TestValidation(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, _ spanstore.Writer, sr spanstore.Reader) {\n\t\ttid := time.Now()\n\t\tparams := &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: tid,\n\t\t\tStartTimeMax: tid.Add(time.Duration(10)),\n\t\t}\n\n\t\tparams.OperationName = \"no-service\"\n\t\t_, err := sr.FindTraces(context.Background(), params)\n\t\trequire.EqualError(t, err, \"service name must be set\")\n\t\tparams.ServiceName = \"find-service\"\n\n\t\t_, err = sr.FindTraces(context.Background(), nil)\n\t\trequire.EqualError(t, err, \"malformed request object\")\n\n\t\tparams.StartTimeMin = params.StartTimeMax.Add(1 * time.Hour)\n\t\t_, err = sr.FindTraces(context.Background(), params)\n\t\trequire.EqualError(t, err, \"min start time is above max\")\n\t\tparams.StartTimeMin = tid\n\n\t\tparams.DurationMax = time.Duration(1 * time.Millisecond)\n\t\tparams.DurationMin = time.Duration(1 * time.Minute)\n\t\t_, err = sr.FindTraces(context.Background(), params)\n\t\trequire.EqualError(t, err, \"min duration is above max\")\n\n\t\tparams = &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: tid,\n\t\t}\n\t\t_, err = sr.FindTraces(context.Background(), params)\n\t\trequire.EqualError(t, err, \"start and end time must be set\")\n\n\t\tparams.StartTimeMax = tid.Add(1 * time.Minute)\n\t\tparams.Tags = map[string]string{\"A\": \"B\"}\n\t\t_, err = sr.FindTraces(context.Background(), params)\n\t\trequire.EqualError(t, err, \"service name must be set\")\n\t})\n}\n\nfunc TestIndexSeeks(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, sw spanstore.Writer, sr spanstore.Reader) {\n\t\tstartT := time.Now()\n\t\ttraces := 60\n\t\tspans := 3\n\t\ttid := startT\n\n\t\ttraceOrder := make([]uint64, traces)\n\n\t\tfor i := range traces {\n\t\t\tlowId := rand.Uint64()\n\t\t\ttraceOrder[i] = lowId\n\t\t\ttid = tid.Add(time.Duration(time.Millisecond * time.Duration(i)))\n\n\t\t\tfor j := range spans {\n\t\t\t\ts := model.Span{\n\t\t\t\t\tTraceID: model.TraceID{\n\t\t\t\t\t\tLow:  lowId,\n\t\t\t\t\t\tHigh: 1,\n\t\t\t\t\t},\n\t\t\t\t\tSpanID:        model.SpanID(rand.Uint64()),\n\t\t\t\t\tOperationName: fmt.Sprintf(\"operation-%d\", j),\n\t\t\t\t\tProcess: &model.Process{\n\t\t\t\t\t\tServiceName: fmt.Sprintf(\"service-%d\", i%4),\n\t\t\t\t\t},\n\t\t\t\t\tStartTime: tid,\n\t\t\t\t\tDuration:  time.Duration(time.Duration(i+j) * time.Millisecond),\n\t\t\t\t\tTags: model.KeyValues{\n\t\t\t\t\t\tmodel.KeyValue{\n\t\t\t\t\t\t\tKey:   fmt.Sprintf(\"k%d\", i),\n\t\t\t\t\t\t\tVStr:  fmt.Sprintf(\"val%d\", j),\n\t\t\t\t\t\t\tVType: model.StringType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tKey:   \"error\",\n\t\t\t\t\t\t\tVType: model.BoolType,\n\t\t\t\t\t\t\tVBool: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr := sw.WriteSpan(context.Background(), &s)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\n\t\ttestOrder := func(trs []*model.Trace) {\n\t\t\t// Assert that we returned correctly in DESC time order\n\t\t\tfor l := 1; l < len(trs); l++ {\n\t\t\t\tassert.True(t, trs[l].Spans[spans-1].StartTime.Before(trs[l-1].Spans[spans-1].StartTime))\n\t\t\t}\n\t\t}\n\n\t\tparams := &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: startT,\n\t\t\tStartTimeMax: startT.Add(time.Duration(time.Millisecond * 10)),\n\t\t\tServiceName:  \"service-1\",\n\t\t}\n\n\t\ttrs, err := sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 1)\n\t\tassert.Len(t, trs[0].Spans, spans)\n\n\t\tparams.OperationName = \"operation-1\"\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 1)\n\n\t\tparams.ServiceName = \"service-10\" // this should not match\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, trs)\n\n\t\tparams.OperationName = \"operation-4\"\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, trs)\n\n\t\t// Multi-index hits\n\n\t\tparams.StartTimeMax = startT.Add(time.Duration(time.Millisecond * 666))\n\t\tparams.ServiceName = \"service-3\"\n\t\tparams.OperationName = \"operation-1\"\n\t\ttags := make(map[string]string)\n\t\ttags[\"k11\"] = \"val0\"\n\t\ttags[\"error\"] = \"true\"\n\t\tparams.Tags = tags\n\t\tparams.DurationMin = time.Duration(1 * time.Millisecond)\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 1)\n\t\tassert.Len(t, trs[0].Spans, spans)\n\n\t\t// Query limited amount of hits\n\n\t\tparams.StartTimeMax = startT.Add(time.Duration(time.Hour * 1))\n\t\tdelete(params.Tags, \"k11\")\n\t\tparams.NumTraces = 2\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 2)\n\t\tassert.Equal(t, traceOrder[59], trs[0].Spans[0].TraceID.Low)\n\t\tassert.Equal(t, traceOrder[55], trs[1].Spans[0].TraceID.Low)\n\t\ttestOrder(trs)\n\n\t\t// Check for DESC return order with duration index\n\t\tparams = &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: startT,\n\t\t\tStartTimeMax: startT.Add(time.Duration(time.Hour * 1)),\n\t\t\tDurationMin:  time.Duration(30 * time.Millisecond), // Filters one\n\t\t\tDurationMax:  time.Duration(50 * time.Millisecond), // Filters three\n\t\t\tNumTraces:    9,\n\t\t}\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 9) // Returns 23, we limited to 9\n\n\t\t// Check the newest items are returned\n\t\tassert.Equal(t, traceOrder[50], trs[0].Spans[0].TraceID.Low)\n\t\tassert.Equal(t, traceOrder[42], trs[8].Spans[0].TraceID.Low)\n\t\ttestOrder(trs)\n\n\t\t// Check for DESC return order without duration index, but still with limit\n\t\tparams.DurationMin = 0\n\t\tparams.DurationMax = 0\n\t\tparams.NumTraces = 7\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 7)\n\t\tassert.Equal(t, traceOrder[59], trs[0].Spans[0].TraceID.Low)\n\t\tassert.Equal(t, traceOrder[53], trs[6].Spans[0].TraceID.Low)\n\t\ttestOrder(trs)\n\n\t\t// StartTime, endTime scan - full table scan (so technically no index seek)\n\t\tparams = &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: startT,\n\t\t\tStartTimeMax: startT.Add(time.Duration(time.Millisecond * 10)),\n\t\t}\n\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 5)\n\t\tassert.Len(t, trs[0].Spans, spans)\n\t\ttestOrder(trs)\n\n\t\t// StartTime and Duration queries\n\t\tparams.StartTimeMax = startT.Add(time.Duration(time.Hour * 10))\n\t\tparams.DurationMin = time.Duration(53 * time.Millisecond) // trace 51 (min)\n\t\tparams.DurationMax = time.Duration(56 * time.Millisecond) // trace 56 (max)\n\n\t\ttrs, err = sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, trs, 6)\n\t\tassert.Equal(t, traceOrder[56], trs[0].Spans[0].TraceID.Low)\n\t\tassert.Equal(t, traceOrder[51], trs[5].Spans[0].TraceID.Low)\n\t\ttestOrder(trs)\n\t})\n}\n\nfunc TestFindNothing(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, _ spanstore.Writer, sr spanstore.Reader) {\n\t\tstartT := time.Now()\n\t\tparams := &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: startT,\n\t\t\tStartTimeMax: startT.Add(time.Duration(time.Millisecond * 10)),\n\t\t\tServiceName:  \"service-1\",\n\t\t}\n\n\t\ttrs, err := sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, trs)\n\n\t\ttr, err := sr.GetTrace(context.Background(), spanstore.GetTraceParameters{TraceID: model.TraceID{Low: 0, High: 0}})\n\t\tassert.Equal(t, spanstore.ErrTraceNotFound, err)\n\t\tassert.Nil(t, tr)\n\t})\n}\n\nfunc TestWriteDuplicates(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, sw spanstore.Writer, _ spanstore.Reader) {\n\t\ttid := time.Now()\n\t\ttimes := 40\n\t\tspans := 3\n\t\tfor i := range times {\n\t\t\tfor j := range spans {\n\t\t\t\ts := model.Span{\n\t\t\t\t\tTraceID: model.TraceID{\n\t\t\t\t\t\tLow:  uint64(0),\n\t\t\t\t\t\tHigh: 1,\n\t\t\t\t\t},\n\t\t\t\t\tSpanID:        model.SpanID(j),\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t\tProcess: &model.Process{\n\t\t\t\t\t\tServiceName: \"service\",\n\t\t\t\t\t},\n\t\t\t\t\tStartTime: tid.Add(time.Duration(10)),\n\t\t\t\t\tDuration:  time.Duration(i + j),\n\t\t\t\t}\n\t\t\t\terr := sw.WriteSpan(context.Background(), &s)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestMenuSeeks(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, sw spanstore.Writer, sr spanstore.Reader) {\n\t\ttid := time.Now()\n\t\ttraces := 40\n\t\tservices := 4\n\t\tspans := 3\n\t\tfor i := range traces {\n\t\t\tfor j := range spans {\n\t\t\t\ts := model.Span{\n\t\t\t\t\tTraceID: model.TraceID{\n\t\t\t\t\t\tLow:  uint64(i),\n\t\t\t\t\t\tHigh: 1,\n\t\t\t\t\t},\n\t\t\t\t\tSpanID:        model.SpanID(j),\n\t\t\t\t\tOperationName: fmt.Sprintf(\"operation-%d\", j),\n\t\t\t\t\tProcess: &model.Process{\n\t\t\t\t\t\tServiceName: fmt.Sprintf(\"service-%d\", i%services),\n\t\t\t\t\t},\n\t\t\t\t\tStartTime: tid.Add(time.Duration(i)),\n\t\t\t\t\tDuration:  time.Duration(i + j),\n\t\t\t\t}\n\t\t\t\terr := sw.WriteSpan(context.Background(), &s)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\n\t\toperations, err := sr.GetOperations(\n\t\t\tcontext.Background(),\n\t\t\tspanstore.OperationQueryParameters{ServiceName: \"service-1\"},\n\t\t)\n\t\trequire.NoError(t, err)\n\n\t\tserviceList, err := sr.GetServices(context.Background())\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, operations, spans)\n\t\tassert.Len(t, serviceList, services)\n\t})\n}\n\nfunc TestPersist(t *testing.T) {\n\tdir := t.TempDir()\n\n\tp := func(t *testing.T, dir string, test func(t *testing.T, sw spanstore.Writer, sr spanstore.Reader)) {\n\t\tf := badger.NewFactory()\n\t\tf.Config.Ephemeral = false\n\t\tf.Config.Directories.Keys = dir\n\t\tf.Config.Directories.Values = dir\n\t\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\trequire.NoError(t, f.Close())\n\t\t}()\n\n\t\tsw, err := f.CreateSpanWriter()\n\t\trequire.NoError(t, err)\n\n\t\tsr, err := f.CreateSpanReader()\n\t\trequire.NoError(t, err)\n\n\t\ttest(t, sw, sr)\n\t}\n\n\tp(t, dir, func(t *testing.T, sw spanstore.Writer, _ spanstore.Reader) {\n\t\ts := model.Span{\n\t\t\tTraceID: model.TraceID{\n\t\t\t\tLow:  uint64(1),\n\t\t\t\tHigh: 1,\n\t\t\t},\n\t\t\tSpanID:        model.SpanID(4),\n\t\t\tOperationName: \"operation-p\",\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"service-p\",\n\t\t\t},\n\t\t\tStartTime: time.Now(),\n\t\t\tDuration:  time.Duration(1 * time.Hour),\n\t\t}\n\t\terr := sw.WriteSpan(context.Background(), &s)\n\t\trequire.NoError(t, err)\n\t})\n\n\tp(t, dir, func(t *testing.T, _ spanstore.Writer, sr spanstore.Reader) {\n\t\ttrace, err := sr.GetTrace(context.Background(), spanstore.GetTraceParameters{TraceID: model.TraceID{\n\t\t\tLow:  uint64(1),\n\t\t\tHigh: 1,\n\t\t}})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"operation-p\", trace.Spans[0].OperationName)\n\n\t\tservices, err := sr.GetServices(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, services, 1)\n\t})\n}\n\n// Opens a badger db and runs a test on it.\nfunc runFactoryTest(tb testing.TB, test func(tb testing.TB, sw spanstore.Writer, sr spanstore.Reader)) {\n\tf := badger.NewFactory()\n\tf.Config.Ephemeral = true\n\tf.Config.SyncWrites = false\n\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\trequire.NoError(tb, err)\n\tdefer func() {\n\t\trequire.NoError(tb, f.Close())\n\t}()\n\n\tsw, err := f.CreateSpanWriter()\n\trequire.NoError(tb, err)\n\n\tsr, err := f.CreateSpanReader()\n\trequire.NoError(tb, err)\n\n\ttest(tb, sw, sr)\n}\n\n// Benchmarks intended for profiling\n\nfunc writeSpans(sw spanstore.Writer, tags []model.KeyValue, services, operations []string, traces, spans int, high uint64, tid time.Time) {\n\tfor i := range traces {\n\t\tfor j := range spans {\n\t\t\ts := model.Span{\n\t\t\t\tTraceID: model.TraceID{\n\t\t\t\t\tLow:  uint64(i),\n\t\t\t\t\tHigh: high,\n\t\t\t\t},\n\t\t\t\tSpanID:        model.SpanID(j),\n\t\t\t\tOperationName: operations[j],\n\t\t\t\tProcess: &model.Process{\n\t\t\t\t\tServiceName: services[j],\n\t\t\t\t},\n\t\t\t\tTags:      tags,\n\t\t\t\tStartTime: tid.Add(time.Duration(time.Millisecond)),\n\t\t\t\tDuration:  time.Duration(time.Millisecond * time.Duration(i+j)),\n\t\t\t}\n\t\t\t_ = sw.WriteSpan(context.Background(), &s)\n\t\t}\n\t}\n}\n\nfunc BenchmarkWrites(b *testing.B) {\n\trunFactoryTest(b, func(_ testing.TB, sw spanstore.Writer, _ spanstore.Reader) {\n\t\ttid := time.Now()\n\t\ttraces := 1000\n\t\tspans := 32\n\t\ttagsCount := 64\n\t\ttags, services, operations := makeWriteSupports(tagsCount, spans)\n\n\t\tf, err := os.Create(\"writes.out\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"could not create CPU profile: \", err)\n\t\t}\n\t\tif err := pprof.StartCPUProfile(f); err != nil {\n\t\t\tlog.Fatal(\"could not start CPU profile: \", err)\n\t\t}\n\t\tdefer pprof.StopCPUProfile()\n\n\t\tb.ResetTimer()\n\t\tfor a := 0; a < b.N; a++ {\n\t\t\twriteSpans(sw, tags, services, operations, traces, spans, uint64(0), tid)\n\t\t}\n\t\tb.StopTimer()\n\t})\n}\n\nfunc makeWriteSupports(tagsCount, spans int) (tags []model.KeyValue, services []string, operations []string) {\n\ttags = make([]model.KeyValue, tagsCount)\n\tfor i := range tagsCount {\n\t\ttags[i] = model.KeyValue{\n\t\t\tKey:  fmt.Sprintf(\"a%d\", i),\n\t\t\tVStr: fmt.Sprintf(\"b%d\", i),\n\t\t}\n\t}\n\toperations = make([]string, spans)\n\tfor j := range spans {\n\t\toperations[j] = fmt.Sprintf(\"operation-%d\", j)\n\t}\n\tservices = make([]string, spans)\n\tfor i := range spans {\n\t\tservices[i] = fmt.Sprintf(\"service-%d\", i)\n\t}\n\n\treturn tags, services, operations\n}\n\nfunc makeReadBenchmark(b *testing.B, _ time.Time, params *spanstore.TraceQueryParameters, outputFile string) {\n\trunLargeFactoryTest(b, func(_ testing.TB, sw spanstore.Writer, sr spanstore.Reader) {\n\t\ttid := time.Now()\n\n\t\t// Total amount of traces is traces * tracesTimes\n\t\ttraces := 1000\n\t\ttracesTimes := 1\n\n\t\t// Total amount of spans written is traces * tracesTimes * spans\n\t\tspans := 32\n\n\t\t// Default is 160k\n\n\t\ttagsCount := 64\n\t\ttags, services, operations := makeWriteSupports(tagsCount, spans)\n\n\t\tfor h := range tracesTimes {\n\t\t\twriteSpans(sw, tags, services, operations, traces, spans, uint64(h), tid)\n\t\t}\n\n\t\tf, err := os.Create(outputFile)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"could not create CPU profile: \", err)\n\t\t}\n\t\tif err := pprof.StartCPUProfile(f); err != nil {\n\t\t\tlog.Fatal(\"could not start CPU profile: \", err)\n\t\t}\n\t\tdefer pprof.StopCPUProfile()\n\n\t\tb.ResetTimer()\n\t\tfor a := 0; a < b.N; a++ {\n\t\t\tsr.FindTraces(context.Background(), params)\n\t\t}\n\t\tb.StopTimer()\n\t})\n}\n\nfunc BenchmarkServiceTagsRangeQueryLimitIndexFetch(b *testing.B) {\n\ttid := time.Now()\n\tparams := &spanstore.TraceQueryParameters{\n\t\tStartTimeMin: tid,\n\t\tStartTimeMax: tid.Add(time.Duration(time.Millisecond * 2000)),\n\t\tServiceName:  \"service-1\",\n\t\tTags: map[string]string{\n\t\t\t\"a8\": \"b8\",\n\t\t},\n\t}\n\n\tparams.DurationMin = time.Duration(1 * time.Millisecond) // durationQuery takes 53% of total execution time..\n\tparams.NumTraces = 50\n\n\tmakeReadBenchmark(b, tid, params, \"scanrangeandindexlimit.out\")\n}\n\nfunc BenchmarkServiceIndexLimitFetch(b *testing.B) {\n\ttid := time.Now()\n\tparams := &spanstore.TraceQueryParameters{\n\t\tStartTimeMin: tid,\n\t\tStartTimeMax: tid.Add(time.Duration(time.Millisecond * 2000)),\n\t\tServiceName:  \"service-1\",\n\t}\n\n\tparams.NumTraces = 50\n\n\tmakeReadBenchmark(b, tid, params, \"serviceindexlimit.out\")\n}\n\n// Opens a badger db and runs a test on it.\nfunc runLargeFactoryTest(tb testing.TB, test func(tb testing.TB, sw spanstore.Writer, sr spanstore.Reader)) {\n\tassertion := require.New(tb)\n\tf := badger.NewFactory()\n\n\tdir := filepath.Join(tb.TempDir(), \"badger-testRun\")\n\terr := os.MkdirAll(dir, 0o700)\n\tassertion.NoError(err)\n\tf.Config.Directories.Keys = dir\n\tf.Config.Directories.Values = dir\n\tf.Config.Ephemeral = false\n\tf.Config.SyncWrites = false\n\n\terr = f.Initialize(metrics.NullFactory, zap.NewNop())\n\tassertion.NoError(err)\n\tdefer func() {\n\t\terr := f.Close()\n\t\tos.RemoveAll(dir)\n\t\trequire.NoError(tb, err)\n\t}()\n\n\tsw, err := f.CreateSpanWriter()\n\tassertion.NoError(err)\n\n\tsr, err := f.CreateSpanReader()\n\tassertion.NoError(err)\n\n\ttest(tb, sw, sr)\n}\n\n// TestRandomTraceID from issue #1808\nfunc TestRandomTraceID(t *testing.T) {\n\trunFactoryTest(t, func(_ testing.TB, sw spanstore.Writer, sr spanstore.Reader) {\n\t\ts1 := model.Span{\n\t\t\tTraceID: model.TraceID{\n\t\t\t\tLow:  uint64(14767110704788176287),\n\t\t\t\tHigh: 0,\n\t\t\t},\n\t\t\tSpanID:        model.SpanID(14976775253976086374),\n\t\t\tOperationName: \"/\",\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"nginx\",\n\t\t\t},\n\t\t\tTags: model.KeyValues{\n\t\t\t\tmodel.KeyValue{\n\t\t\t\t\tKey:   \"http.request_id\",\n\t\t\t\t\tVStr:  \"first\",\n\t\t\t\t\tVType: model.StringType,\n\t\t\t\t},\n\t\t\t},\n\t\t\tStartTime: time.Now(),\n\t\t\tDuration:  1 * time.Second,\n\t\t}\n\t\terr := sw.WriteSpan(context.Background(), &s1)\n\t\trequire.NoError(t, err)\n\n\t\ts2 := model.Span{\n\t\t\tTraceID: model.TraceID{\n\t\t\t\tLow:  uint64(4775132888371984950),\n\t\t\t\tHigh: 0,\n\t\t\t},\n\t\t\tSpanID:        model.SpanID(13576481569227028654),\n\t\t\tOperationName: \"/\",\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"nginx\",\n\t\t\t},\n\t\t\tTags: model.KeyValues{\n\t\t\t\tmodel.KeyValue{\n\t\t\t\t\tKey:   \"http.request_id\",\n\t\t\t\t\tVStr:  \"second\",\n\t\t\t\t\tVType: model.StringType,\n\t\t\t\t},\n\t\t\t},\n\t\t\tStartTime: time.Now(),\n\t\t\tDuration:  1 * time.Second,\n\t\t}\n\t\terr = sw.WriteSpan(context.Background(), &s2)\n\t\trequire.NoError(t, err)\n\n\t\tparams := &spanstore.TraceQueryParameters{\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Minute),\n\t\t\tStartTimeMax: time.Now(),\n\t\t\tServiceName:  \"nginx\",\n\t\t\tTags: map[string]string{\n\t\t\t\t\"http.request_id\": \"second\",\n\t\t\t},\n\t\t}\n\t\ttraces, err := sr.FindTraces(context.Background(), params)\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 1)\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/reader.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\t\"golang.org/x/exp/maps\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// Most of these errors are common with the ES and Cassandra backends. Each backend has slightly different validation rules.\n\nvar (\n\t// ErrServiceNameNotSet occurs when attempting to query with an empty service name\n\tErrServiceNameNotSet = errors.New(\"service name must be set\")\n\n\t// ErrStartTimeMinGreaterThanMax occurs when start time min is above start time max\n\tErrStartTimeMinGreaterThanMax = errors.New(\"min start time is above max\")\n\n\t// ErrDurationMinGreaterThanMax occurs when duration min is above duration max\n\tErrDurationMinGreaterThanMax = errors.New(\"min duration is above max\")\n\n\t// ErrMalformedRequestObject occurs when a request object is nil\n\tErrMalformedRequestObject = errors.New(\"malformed request object\")\n\n\t// ErrStartAndEndTimeNotSet occurs when start time and end time are not set\n\tErrStartAndEndTimeNotSet = errors.New(\"start and end time must be set\")\n\n\t// ErrUnableToFindTraceIDAggregation occurs when an aggregation query for TraceIDs fail.\n\tErrUnableToFindTraceIDAggregation = errors.New(\"could not find aggregation of traceIDs\")\n\n\t// ErrNotSupported during development, don't support every option - yet\n\tErrNotSupported = errors.New(\"this query parameter is not supported yet\")\n\n\t// ErrInternalConsistencyError indicates internal data consistency issue\n\tErrInternalConsistencyError = errors.New(\"internal data consistency issue\")\n)\n\nconst (\n\tdefaultNumTraces = 100\n\tsizeOfTraceID    = 16\n\tencodingTypeBits = 0x0F\n)\n\n// TraceReader reads traces from the local badger store\ntype TraceReader struct {\n\tstore *badger.DB\n\tcache *CacheStore\n}\n\n// executionPlan is internal structure to track the index filtering\ntype executionPlan struct {\n\tstartTimeMin []byte\n\tstartTimeMax []byte\n\n\tlimit int\n\n\t// mergeOuter is the result of merge-join of inner and outer result sets\n\tmergeOuter [][]byte\n\n\t// hashOuter is the hashmap for hash-join of outer resultset\n\thashOuter map[model.TraceID]struct{}\n}\n\n// NewTraceReader returns a TraceReader with cache\nfunc NewTraceReader(db *badger.DB, c *CacheStore, prefillCache bool) *TraceReader {\n\treader := &TraceReader{\n\t\tstore: db,\n\t\tcache: c,\n\t}\n\tif prefillCache {\n\t\tservices := reader.preloadServices()\n\t\tfor _, service := range services {\n\t\t\treader.preloadOperations(service)\n\t\t}\n\t}\n\treturn reader\n}\n\nfunc decodeValue(val []byte, encodeType byte) (*model.Span, error) {\n\tsp := model.Span{}\n\tswitch encodeType {\n\tcase jsonEncoding:\n\t\tif err := json.Unmarshal(val, &sp); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase protoEncoding:\n\t\tif err := sp.Unmarshal(val); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown encoding type: %#02x\", encodeType)\n\t}\n\treturn &sp, nil\n}\n\n// getTraces enriches TraceIDs to Traces\nfunc (r *TraceReader) getTraces(traceIDs []model.TraceID) ([]*model.Trace, error) {\n\t// Get by PK\n\ttraces := make([]*model.Trace, 0, len(traceIDs))\n\tprefixes := make([][]byte, 0, len(traceIDs))\n\n\tfor _, traceID := range traceIDs {\n\t\tprefixes = append(prefixes, createPrimaryKeySeekPrefix(traceID))\n\t}\n\n\terr := r.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\tval := []byte{}\n\t\tfor _, prefix := range prefixes {\n\t\t\tspans := make([]*model.Span, 0, 32) // reduce reallocation requirements by defining some initial length\n\n\t\t\tfor it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {\n\t\t\t\t// Add value to the span store (decode from JSON / defined encoding first)\n\t\t\t\t// These are in the correct order because of the sorted nature\n\t\t\t\titem := it.Item()\n\t\t\t\tval, err := item.ValueCopy(val)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tsp, err := decodeValue(val, item.UserMeta()&encodingTypeBits)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tspans = append(spans, sp)\n\t\t\t}\n\t\t\tif len(spans) > 0 {\n\t\t\t\ttrace := &model.Trace{\n\t\t\t\t\tSpans: spans,\n\t\t\t\t}\n\t\t\t\ttraces = append(traces, trace)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn traces, err\n}\n\n// GetTrace takes a traceID and returns a Trace associated with that traceID\nfunc (r *TraceReader) GetTrace(_ context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\ttraces, err := r.getTraces([]model.TraceID{query.TraceID})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(traces) == 0 {\n\t\treturn nil, spanstore.ErrTraceNotFound\n\t}\n\tif len(traces) == 1 {\n\t\treturn traces[0], nil\n\t}\n\n\treturn nil, ErrInternalConsistencyError\n}\n\n// scanTimeRange returns all the Traces found between startTs and endTs\nfunc (r *TraceReader) scanTimeRange(plan *executionPlan) ([]model.TraceID, error) {\n\t// We need to do a full table scan\n\ttraceKeys := make([][]byte, 0)\n\terr := r.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\topts.PrefetchValues = false\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\tstartIndex := []byte{spanKeyPrefix}\n\t\tprevTraceID := []byte{}\n\t\tfor it.Seek(startIndex); it.ValidForPrefix(startIndex); it.Next() {\n\t\t\titem := it.Item()\n\n\t\t\tkey := []byte{}\n\t\t\tkey = item.KeyCopy(key)\n\n\t\t\ttimestamp := key[sizeOfTraceID+1 : sizeOfTraceID+1+8]\n\t\t\ttraceID := key[1 : sizeOfTraceID+1]\n\n\t\t\tif bytes.Compare(timestamp, plan.startTimeMin) >= 0 && bytes.Compare(timestamp, plan.startTimeMax) <= 0 {\n\t\t\t\tif !bytes.Equal(traceID, prevTraceID) {\n\t\t\t\t\tif plan.hashOuter != nil {\n\t\t\t\t\t\ttrID := bytesToTraceID(traceID)\n\t\t\t\t\t\tif _, exists := plan.hashOuter[trID]; exists {\n\t\t\t\t\t\t\ttraceKeys = append(traceKeys, key)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttraceKeys = append(traceKeys, key)\n\t\t\t\t\t}\n\t\t\t\t\tprevTraceID = traceID\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tsort.Slice(traceKeys, func(k, h int) bool {\n\t\t// This sorts by timestamp to descending order\n\t\treturn bytes.Compare(traceKeys[k][sizeOfTraceID+1:sizeOfTraceID+1+8], traceKeys[h][sizeOfTraceID+1:sizeOfTraceID+1+8]) > 0\n\t})\n\n\tsizeCount := len(traceKeys)\n\tif plan.limit > 0 && plan.limit < sizeCount {\n\t\tsizeCount = plan.limit\n\t}\n\ttraceIDs := make([]model.TraceID, sizeCount)\n\n\tfor i := 0; i < sizeCount; i++ {\n\t\ttraceIDs[i] = bytesToTraceID(traceKeys[i][1 : sizeOfTraceID+1])\n\t}\n\n\treturn traceIDs, err\n}\n\nfunc createPrimaryKeySeekPrefix(traceID model.TraceID) []byte {\n\tkey := make([]byte, 1+sizeOfTraceID)\n\tkey[0] = spanKeyPrefix\n\tpos := 1\n\tbinary.BigEndian.PutUint64(key[pos:], traceID.High)\n\tpos += 8\n\tbinary.BigEndian.PutUint64(key[pos:], traceID.Low)\n\n\treturn key\n}\n\n// GetServices fetches the sorted service list that have not expired\nfunc (r *TraceReader) GetServices(context.Context) ([]string, error) {\n\treturn r.cache.GetServices()\n}\n\n// GetOperations fetches operations in the service and empty slice if service does not exists\nfunc (r *TraceReader) GetOperations(\n\t_ context.Context,\n\tquery spanstore.OperationQueryParameters,\n) ([]spanstore.Operation, error) {\n\treturn r.cache.GetOperations(query.ServiceName)\n}\n\n// setQueryDefaults alters the query with defaults if certain parameters are not set\nfunc setQueryDefaults(query *spanstore.TraceQueryParameters) {\n\tif query.NumTraces <= 0 {\n\t\tquery.NumTraces = defaultNumTraces\n\t}\n}\n\n// serviceQueries parses the query to index seeks which are unique index seeks\nfunc serviceQueries(query *spanstore.TraceQueryParameters, indexSeeks [][]byte) [][]byte {\n\tif query.ServiceName != \"\" {\n\t\tindexSearchKey := make([]byte, 0, 64) // 64 is a magic guess\n\t\ttagQueryUsed := false\n\t\tfor k, v := range query.Tags {\n\t\t\ttagSearch := []byte(query.ServiceName + k + v)\n\t\t\ttagSearchKey := make([]byte, 0, len(tagSearch)+1)\n\t\t\ttagSearchKey = append(tagSearchKey, tagIndexKey)\n\t\t\ttagSearchKey = append(tagSearchKey, tagSearch...)\n\t\t\tindexSeeks = append(indexSeeks, tagSearchKey)\n\t\t\ttagQueryUsed = true\n\t\t}\n\n\t\tif query.OperationName != \"\" {\n\t\t\tindexSearchKey = append(indexSearchKey, operationNameIndexKey)\n\t\t\tindexSearchKey = append(indexSearchKey, []byte(query.ServiceName+query.OperationName)...)\n\t\t} else if !tagQueryUsed { // Tag query already reduces the search set with a serviceName\n\t\t\tindexSearchKey = append(indexSearchKey, serviceNameIndexKey)\n\t\t\tindexSearchKey = append(indexSearchKey, []byte(query.ServiceName)...)\n\t\t}\n\n\t\tif len(indexSearchKey) > 0 {\n\t\t\tindexSeeks = append(indexSeeks, indexSearchKey)\n\t\t}\n\t}\n\treturn indexSeeks\n}\n\n// indexSeeksToTraceIDs does the index scanning against badger based on the parsed index queries\nfunc (r *TraceReader) indexSeeksToTraceIDs(plan *executionPlan, indexSeeks [][]byte) ([]model.TraceID, error) {\n\tfor i := len(indexSeeks) - 1; i > 0; i-- {\n\t\tindexResults, err := r.scanIndexKeys(indexSeeks[i], plan)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tsort.Slice(indexResults, func(k, h int) bool {\n\t\t\treturn bytes.Compare(indexResults[k], indexResults[h]) < 0\n\t\t})\n\n\t\t// Same traceID can be returned multiple times, but always in sorted order so checking the previous key is enough\n\t\tprevTraceID := []byte{}\n\t\tinnerIDs := make([][]byte, 0, len(indexSeeks))\n\t\tfor j := range indexResults {\n\t\t\ttraceID := indexResults[j]\n\t\t\tif !bytes.Equal(prevTraceID, traceID) {\n\t\t\t\tinnerIDs = append(innerIDs, traceID)\n\t\t\t\tprevTraceID = traceID\n\t\t\t}\n\t\t}\n\n\t\t// Merge-join current results\n\t\tif plan.mergeOuter == nil {\n\t\t\tplan.mergeOuter = innerIDs\n\t\t} else {\n\t\t\tplan.mergeOuter = mergeJoinIds(plan.mergeOuter, innerIDs)\n\t\t}\n\t}\n\n\t// Last scan should get us in correct timestamp order\n\tids, err := r.scanIndexKeys(indexSeeks[0], plan)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif plan.mergeOuter != nil {\n\t\t// Build hash of the current merged data\n\t\tplan.hashOuter = buildHash(plan, plan.mergeOuter)\n\t\tplan.mergeOuter = nil\n\t} else {\n\t\t// We filter the last elements\n\t\tplan.hashOuter = buildHash(plan, ids)\n\t}\n\n\ttraceIDs := filterIDs(plan, ids)\n\treturn traceIDs, nil\n}\n\nfunc filterIDs(plan *executionPlan, innerIDs [][]byte) []model.TraceID {\n\ttraces := make([]model.TraceID, 0, plan.limit)\n\n\titems := 0\n\tfor i := range innerIDs {\n\t\ttrID := bytesToTraceID(innerIDs[i])\n\n\t\tif _, found := plan.hashOuter[trID]; found {\n\t\t\ttraces = append(traces, trID)\n\t\t\tdelete(plan.hashOuter, trID) // Prevent duplicate add\n\t\t\titems++\n\t\t}\n\n\t\tif items == plan.limit {\n\t\t\treturn traces\n\t\t}\n\t}\n\n\treturn traces\n}\n\nfunc bytesToTraceID(key []byte) model.TraceID {\n\treturn model.TraceID{\n\t\tHigh: binary.BigEndian.Uint64(key[:8]),\n\t\tLow:  binary.BigEndian.Uint64(key[8:sizeOfTraceID]),\n\t}\n}\n\nfunc buildHash(plan *executionPlan, outerIDs [][]byte) map[model.TraceID]struct{} {\n\tvar empty struct{}\n\n\thashed := make(map[model.TraceID]struct{})\n\tfor i := range outerIDs {\n\t\ttrID := bytesToTraceID(outerIDs[i])\n\n\t\tif plan.hashOuter != nil {\n\t\t\tif _, exists := plan.hashOuter[trID]; exists {\n\t\t\t\thashed[trID] = empty\n\t\t\t\tdelete(plan.hashOuter, trID) // Filter duplications\n\t\t\t}\n\t\t} else {\n\t\t\thashed[trID] = empty\n\t\t}\n\t}\n\n\treturn hashed\n}\n\n// durationQueries checks non unique index of durations and returns a map for further filtering purposes\nfunc (r *TraceReader) durationQueries(plan *executionPlan, query *spanstore.TraceQueryParameters) map[model.TraceID]struct{} {\n\tdurMax := uint64(model.DurationAsMicroseconds(query.DurationMax))\n\tdurMin := uint64(model.DurationAsMicroseconds(query.DurationMin))\n\n\tstartKey := make([]byte, 1+8)\n\tendKey := make([]byte, 1+8)\n\n\tstartKey[0] = durationIndexKey\n\tendKey[0] = durationIndexKey\n\n\tif query.DurationMax == 0 {\n\t\t// Set MAX to infinite, if Min is missing, 0 is a fine search result for us\n\t\tdurMax = math.MaxUint64\n\t}\n\tbinary.BigEndian.PutUint64(endKey[1:], durMax)\n\tbinary.BigEndian.PutUint64(startKey[1:], durMin)\n\n\t// This is not unique index result - same TraceID can be matched from multiple spans\n\tindexResults, _ := r.scanRangeIndex(plan, startKey, endKey)\n\thashFilter := make(map[model.TraceID]struct{})\n\tvar value struct{}\n\tfor _, k := range indexResults {\n\t\tkey := k[len(k)-sizeOfTraceID:]\n\t\tid := model.TraceID{\n\t\t\tHigh: binary.BigEndian.Uint64(key[:8]),\n\t\t\tLow:  binary.BigEndian.Uint64(key[8:]),\n\t\t}\n\t\tif _, exists := hashFilter[id]; !exists {\n\t\t\thashFilter[id] = value\n\t\t}\n\t}\n\n\treturn hashFilter\n}\n\nfunc mergeJoinIds(left, right [][]byte) [][]byte {\n\t// len(left) or len(right) is the maximum, whichever is the smallest\n\tallocateSize := min(len(right), len(left))\n\n\tmerged := make([][]byte, 0, allocateSize)\n\n\tlMax := len(left) - 1\n\trMax := len(right) - 1\n\tfor r, l := 0, 0; r <= rMax && l <= lMax; {\n\t\tswitch bytes.Compare(left[l], right[r]) {\n\t\tcase 1:\n\t\t\t// left > right, increase right one\n\t\t\tr++\n\t\tcase -1:\n\t\t\t// left < right, increase left one\n\t\t\tl++\n\t\tdefault:\n\t\t\t// Left matches right (case 0) - merge\n\t\t\t// #nosec G602 loop condition ensures l < len(left)\n\t\t\tmerged = append(merged, left[l])\n\t\t\t// Advance both\n\t\t\tl++\n\t\t\tr++\n\t\t}\n\t}\n\treturn merged\n}\n\n// FindTraces retrieves traces that match the traceQuery\nfunc (r *TraceReader) FindTraces(ctx context.Context, query *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\tkeys, err := r.FindTraceIDs(ctx, query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r.getTraces(keys)\n}\n\n// FindTraceIDs retrieves only the TraceIDs that match the traceQuery, but not the trace data\nfunc (r *TraceReader) FindTraceIDs(_ context.Context, query *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\t// Validate and set query defaults which were not defined\n\tif err := validateQuery(query); err != nil {\n\t\treturn nil, err\n\t}\n\n\tsetQueryDefaults(query)\n\n\t// Find matches using indexes that are using service as part of the key\n\tindexSeeks := make([][]byte, 0, 1)\n\tindexSeeks = serviceQueries(query, indexSeeks)\n\n\tstartStampBytes := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(startStampBytes, model.TimeAsEpochMicroseconds(query.StartTimeMin))\n\n\tendStampBytes := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(endStampBytes, model.TimeAsEpochMicroseconds(query.StartTimeMax))\n\n\tplan := &executionPlan{\n\t\tstartTimeMin: startStampBytes,\n\t\tstartTimeMax: endStampBytes,\n\t\tlimit:        query.NumTraces,\n\t}\n\n\tif query.DurationMax != 0 || query.DurationMin != 0 {\n\t\tplan.hashOuter = r.durationQueries(plan, query)\n\t}\n\n\tif len(indexSeeks) > 0 {\n\t\tkeys, err := r.indexSeeksToTraceIDs(plan, indexSeeks)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn keys, nil\n\t}\n\n\treturn r.scanTimeRange(plan)\n}\n\n// validateQuery returns an error if certain restrictions are not met\nfunc validateQuery(p *spanstore.TraceQueryParameters) error {\n\tif p == nil {\n\t\treturn ErrMalformedRequestObject\n\t}\n\tif p.ServiceName == \"\" && len(p.Tags) > 0 {\n\t\treturn ErrServiceNameNotSet\n\t}\n\tif p.ServiceName == \"\" && p.OperationName != \"\" {\n\t\treturn ErrServiceNameNotSet\n\t}\n\tif p.StartTimeMin.IsZero() || p.StartTimeMax.IsZero() {\n\t\treturn ErrStartAndEndTimeNotSet\n\t}\n\tif !p.StartTimeMax.IsZero() && p.StartTimeMax.Before(p.StartTimeMin) {\n\t\treturn ErrStartTimeMinGreaterThanMax\n\t}\n\tif p.DurationMin != 0 && p.DurationMax != 0 && p.DurationMin > p.DurationMax {\n\t\treturn ErrDurationMinGreaterThanMax\n\t}\n\treturn nil\n}\n\n// scanIndexKeys scans the time range for index keys matching the given prefix.\nfunc (r *TraceReader) scanIndexKeys(indexKeyValue []byte, plan *executionPlan) ([][]byte, error) {\n\tindexResults := make([][]byte, 0)\n\n\terr := r.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\topts.PrefetchValues = false // Don't fetch values since we're only interested in the keys\n\t\topts.Reverse = true\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\t// Create starting point for sorted index scan\n\t\tstartIndex := make([]byte, len(indexKeyValue)+8+1)\n\t\tstartIndex[len(startIndex)-1] = 0xFF\n\t\tcopy(startIndex, indexKeyValue)\n\t\tcopy(startIndex[len(indexKeyValue):], plan.startTimeMax)\n\n\t\tfor it.Seek(startIndex); scanFunction(it, indexKeyValue, plan.startTimeMin); it.Next() {\n\t\t\titem := it.Item()\n\n\t\t\t// ScanFunction is a prefix scanning (since we could have for example service1 & service12)\n\t\t\t// Now we need to match only the exact key if we want to add it\n\t\t\ttimestampStartIndex := len(it.Item().Key()) - (sizeOfTraceID + 8) // timestamp is stored with 8 bytes\n\t\t\tif bytes.Equal(indexKeyValue, it.Item().Key()[:timestampStartIndex]) {\n\t\t\t\ttraceIDBytes := item.Key()[len(item.Key())-sizeOfTraceID:]\n\n\t\t\t\ttraceIDCopy := make([]byte, sizeOfTraceID)\n\t\t\t\tcopy(traceIDCopy, traceIDBytes)\n\t\t\t\tindexResults = append(indexResults, traceIDCopy)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn indexResults, err\n}\n\n// scanFunction compares the index name as well as the time range in the index key\nfunc scanFunction(it *badger.Iterator, indexPrefix []byte, timeBytesEnd []byte) bool {\n\tif it.Valid() {\n\t\t// We can't use the indexPrefix length, because we might have the same prefixValue for non-matching cases also\n\t\ttimestampStartIndex := len(it.Item().Key()) - (sizeOfTraceID + 8) // timestamp is stored with 8 bytes\n\t\ttimestamp := it.Item().Key()[timestampStartIndex : timestampStartIndex+8]\n\t\ttimestampInRange := bytes.Compare(timeBytesEnd, timestamp) <= 0\n\n\t\t// Check length as well to prevent theoretical case where timestamp might match with wrong index key\n\t\tif len(it.Item().Key()) != len(indexPrefix)+24 {\n\t\t\treturn false\n\t\t}\n\n\t\treturn bytes.HasPrefix(it.Item().Key()[:timestampStartIndex], indexPrefix) && timestampInRange\n\t}\n\treturn false\n}\n\n// scanRangeIndex scans the time range for index keys matching the given prefix.\nfunc (r *TraceReader) scanRangeIndex(plan *executionPlan, indexStartValue []byte, indexEndValue []byte) ([][]byte, error) {\n\tindexResults := make([][]byte, 0)\n\n\terr := r.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\topts.PrefetchValues = false // Don't fetch values since we're only interested in the keys\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\t// Create starting point for sorted index scan\n\t\tstartIndex := make([]byte, len(indexStartValue)+len(plan.startTimeMin))\n\t\tcopy(startIndex, indexStartValue)\n\t\tcopy(startIndex[len(indexStartValue):], plan.startTimeMin)\n\n\t\tfor it.Seek(startIndex); scanRangeFunction(it, indexEndValue); it.Next() {\n\t\t\titem := it.Item()\n\n\t\t\t// ScanFunction is a prefix scanning (since we could have for example service1 & service12)\n\t\t\t// Now we need to match only the exact key if we want to add it\n\t\t\ttimestampStartIndex := len(it.Item().Key()) - (sizeOfTraceID + 8) // timestamp is stored with 8 bytes\n\t\t\ttimestamp := it.Item().Key()[timestampStartIndex : timestampStartIndex+8]\n\t\t\tif bytes.Compare(timestamp, plan.startTimeMax) <= 0 {\n\t\t\t\tkey := make([]byte, len(item.Key()))\n\t\t\t\tcopy(key, item.Key())\n\t\t\t\tindexResults = append(indexResults, key)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\treturn indexResults, err\n}\n\n// scanRangeFunction seeks until the index end has been reached\nfunc scanRangeFunction(it *badger.Iterator, indexEndValue []byte) bool {\n\tif it.Valid() {\n\t\tcompareSlice := it.Item().Key()[:len(indexEndValue)]\n\t\treturn bytes.Compare(indexEndValue, compareSlice) >= 0\n\t}\n\treturn false\n}\n\n// preloadServices fills the cache with services after extracting from badger\nfunc (r *TraceReader) preloadServices() []string {\n\tservices := map[string]struct{}{}\n\tr.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\tserviceKey := []byte{serviceNameIndexKey}\n\n\t\t// Seek all the services first\n\t\tfor it.Seek(serviceKey); it.ValidForPrefix(serviceKey); it.Next() {\n\t\t\ttimestampStartIndex := len(it.Item().Key()) - (sizeOfTraceID + 8) // 8 = sizeof(uint64)\n\t\t\tserviceName := string(it.Item().Key()[len(serviceKey):timestampStartIndex])\n\t\t\tkeyTTL := it.Item().ExpiresAt()\n\t\t\tservices[serviceName] = struct{}{}\n\t\t\tr.cache.AddService(serviceName, keyTTL)\n\t\t}\n\t\treturn nil\n\t})\n\treturn maps.Keys(services)\n}\n\n// preloadOperations extract all operations for a specified service\nfunc (r *TraceReader) preloadOperations(service string) {\n\tr.store.View(func(txn *badger.Txn) error {\n\t\topts := badger.DefaultIteratorOptions\n\t\tit := txn.NewIterator(opts)\n\t\tdefer it.Close()\n\n\t\tserviceKey := make([]byte, len(service)+1)\n\t\tserviceKey[0] = operationNameIndexKey\n\t\tcopy(serviceKey[1:], service)\n\n\t\t// Seek all the services first\n\t\tfor it.Seek(serviceKey); it.ValidForPrefix(serviceKey); it.Next() {\n\t\t\ttimestampStartIndex := len(it.Item().Key()) - (sizeOfTraceID + 8) // 8 = sizeof(uint64)\n\t\t\toperationName := string(it.Item().Key()[len(serviceKey):timestampStartIndex])\n\t\t\tkeyTTL := it.Item().ExpiresAt()\n\t\t\tr.cache.AddOperation(service, operationName, keyTTL)\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/rw_internal_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\nfunc TestEncodingTypes(t *testing.T) {\n\t// JSON encoding\n\trunWithBadger(t, func(store *badger.DB, t *testing.T) {\n\t\ttestSpan := createDummySpan()\n\n\t\tcache := NewCacheStore(store, time.Duration(1*time.Hour))\n\t\tsw := NewSpanWriter(store, cache, time.Duration(1*time.Hour))\n\t\trw := NewTraceReader(store, cache, true)\n\n\t\tsw.encodingType = jsonEncoding\n\t\terr := sw.WriteSpan(context.Background(), &testSpan)\n\t\trequire.NoError(t, err)\n\n\t\ttr, err := rw.GetTrace(context.Background(), spanstore.GetTraceParameters{TraceID: model.TraceID{Low: 0, High: 1}})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, tr.Spans, 1)\n\t})\n\n\t// Unknown encoding write\n\trunWithBadger(t, func(store *badger.DB, t *testing.T) {\n\t\ttestSpan := createDummySpan()\n\n\t\tcache := NewCacheStore(store, time.Duration(1*time.Hour))\n\t\tsw := NewSpanWriter(store, cache, time.Duration(1*time.Hour))\n\t\t// rw := NewTraceReader(store, cache)\n\n\t\tsw.encodingType = 0x04\n\t\terr := sw.WriteSpan(context.Background(), &testSpan)\n\t\trequire.EqualError(t, err, \"unknown encoding type: 0x04\")\n\t})\n\n\t// Unknown encoding reader\n\trunWithBadger(t, func(store *badger.DB, t *testing.T) {\n\t\ttestSpan := createDummySpan()\n\n\t\tcache := NewCacheStore(store, time.Duration(1*time.Hour))\n\t\tsw := NewSpanWriter(store, cache, time.Duration(1*time.Hour))\n\t\trw := NewTraceReader(store, cache, true)\n\n\t\terr := sw.WriteSpan(context.Background(), &testSpan)\n\t\trequire.NoError(t, err)\n\n\t\tstartTime := model.TimeAsEpochMicroseconds(testSpan.StartTime)\n\n\t\tkey, _, _ := createTraceKV(&testSpan, protoEncoding, startTime)\n\t\te := &badger.Entry{\n\t\t\tKey:       key,\n\t\t\tExpiresAt: uint64(time.Now().Add(1 * time.Hour).Unix()),\n\t\t}\n\t\te.UserMeta = byte(0x04)\n\n\t\tstore.Update(func(txn *badger.Txn) error {\n\t\t\ttxn.SetEntry(e)\n\t\t\treturn nil\n\t\t})\n\n\t\t_, err = rw.GetTrace(context.Background(), spanstore.GetTraceParameters{TraceID: model.TraceID{Low: 0, High: 1}})\n\t\trequire.EqualError(t, err, \"unknown encoding type: 0x04\")\n\t})\n}\n\nfunc TestDecodeErrorReturns(t *testing.T) {\n\tgarbage := []byte{0x08}\n\n\t_, err := decodeValue(garbage, protoEncoding)\n\trequire.Error(t, err)\n\n\t_, err = decodeValue(garbage, jsonEncoding)\n\trequire.Error(t, err)\n}\n\nfunc TestDuplicateTraceIDDetection(t *testing.T) {\n\trunWithBadger(t, func(store *badger.DB, t *testing.T) {\n\t\ttestSpan := createDummySpan()\n\t\tcache := NewCacheStore(store, time.Duration(1*time.Hour))\n\t\tsw := NewSpanWriter(store, cache, time.Duration(1*time.Hour))\n\t\trw := NewTraceReader(store, cache, true)\n\t\torigStartTime := testSpan.StartTime\n\n\t\ttraceCount := 128\n\t\tfor range traceCount {\n\t\t\ttestSpan.TraceID.Low = rand.Uint64()\n\t\t\tfor range 32 {\n\t\t\t\ttestSpan.SpanID = model.SpanID(rand.Uint64())\n\t\t\t\ttestSpan.StartTime = origStartTime.Add(time.Duration(rand.Int31n(8000)) * time.Millisecond)\n\t\t\t\terr := sw.WriteSpan(context.Background(), &testSpan)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t}\n\n\t\ttraces, err := rw.FindTraceIDs(context.Background(), &spanstore.TraceQueryParameters{\n\t\t\tServiceName:  \"service\",\n\t\t\tNumTraces:    256, // Default is 100, we want to fetch more than there should be\n\t\t\tStartTimeMax: time.Now().Add(time.Hour),\n\t\t\tStartTimeMin: testSpan.StartTime.Add(-1 * time.Hour),\n\t\t})\n\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 128)\n\t})\n}\n\nfunc createDummySpan() model.Span {\n\ttid := time.Now()\n\n\tdummyKv := []model.KeyValue{\n\t\t{\n\t\t\tKey:   \"key\",\n\t\t\tVType: model.StringType,\n\t\t\tVStr:  \"value\",\n\t\t},\n\t}\n\n\ttestSpan := model.Span{\n\t\tTraceID: model.TraceID{\n\t\t\tLow:  uint64(0),\n\t\t\tHigh: 1,\n\t\t},\n\t\tSpanID:        model.SpanID(0),\n\t\tOperationName: \"operation\",\n\t\tProcess: &model.Process{\n\t\t\tServiceName: \"service\",\n\t\t\tTags:        dummyKv,\n\t\t},\n\t\tStartTime: tid.Add(time.Duration(1 * time.Millisecond)),\n\t\tDuration:  time.Duration(1 * time.Millisecond),\n\t\tTags:      dummyKv,\n\t\tLogs: []model.Log{\n\t\t\t{\n\t\t\t\tTimestamp: tid,\n\t\t\t\tFields:    dummyKv,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn testSpan\n}\n\nfunc TestMergeJoin(t *testing.T) {\n\tchk := assert.New(t)\n\n\t// Test equals\n\n\tleft := make([][]byte, 16)\n\tright := make([][]byte, 16)\n\n\tfor i := range 16 {\n\t\tleft[i] = make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(left[i], uint32(i))\n\n\t\tright[i] = make([]byte, 4)\n\t\tbinary.BigEndian.PutUint32(right[i], uint32(i))\n\t}\n\n\tmerged := mergeJoinIds(left, right)\n\tchk.Len(merged, 16)\n\n\t// Check order\n\tchk.Equal(uint32(15), binary.BigEndian.Uint32(merged[15]))\n\n\t// Test simple non-equality different size\n\n\tmerged = mergeJoinIds(left[1:2], right[13:])\n\tchk.Empty(merged)\n\n\t// Different size, some equalities\n\n\tmerged = mergeJoinIds(left[0:3], right[1:7])\n\tchk.Len(merged, 2)\n\tchk.Equal(uint32(2), binary.BigEndian.Uint32(merged[1]))\n}\n\nfunc TestOldReads(t *testing.T) {\n\trunWithBadger(t, func(store *badger.DB, t *testing.T) {\n\t\ttimeNow := model.TimeAsEpochMicroseconds(time.Now())\n\t\ts1Key := createIndexKey(serviceNameIndexKey, []byte(\"service1\"), timeNow, model.TraceID{High: 0, Low: 0})\n\t\ts1o1Key := createIndexKey(operationNameIndexKey, []byte(\"service1operation1\"), timeNow, model.TraceID{High: 0, Low: 0})\n\n\t\ttid := time.Now().Add(1 * time.Minute)\n\n\t\twriter := func() {\n\t\t\tstore.Update(func(txn *badger.Txn) error {\n\t\t\t\ttxn.SetEntry(&badger.Entry{\n\t\t\t\t\tKey:       s1Key,\n\t\t\t\t\tExpiresAt: uint64(tid.Unix()),\n\t\t\t\t})\n\t\t\t\ttxn.SetEntry(&badger.Entry{\n\t\t\t\t\tKey:       s1o1Key,\n\t\t\t\t\tExpiresAt: uint64(tid.Unix()),\n\t\t\t\t})\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\n\t\tcache := NewCacheStore(store, time.Duration(-1*time.Hour))\n\t\twriter()\n\n\t\tnuTid := tid.Add(1 * time.Hour)\n\n\t\tcache.Update(\"service1\", \"operation1\", uint64(tid.Unix()))\n\t\tcache.services[\"service1\"] = uint64(nuTid.Unix())\n\t\tcache.operations[\"service1\"][\"operation1\"] = uint64(nuTid.Unix())\n\n\t\t// This is equivalent to populate caches of cache\n\t\t_ = NewTraceReader(store, cache, true)\n\n\t\t// Now make sure we didn't use the older timestamps from the DB\n\t\tassert.Equal(t, uint64(nuTid.Unix()), cache.services[\"service1\"])\n\t\tassert.Equal(t, uint64(nuTid.Unix()), cache.operations[\"service1\"][\"operation1\"])\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/spanstore/writer.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/dgraph-io/badger/v4\"\n\t\"github.com/gogo/protobuf/proto\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n/*\n\tThis store should be easily modified to use any sorted KV-store, which allows set/get/iterators.\n\tThat includes RocksDB also (this key structure should work as-is with RocksDB)\n\n\tKeys are written in BigEndian order to allow lexicographic sorting of keys\n*/\n\nconst (\n\tspanKeyPrefix         byte = 0x80 // All span keys should have first bit set to 1\n\tindexKeyRange         byte = 0x0F // Secondary indexes use last 4 bits\n\tserviceNameIndexKey   byte = 0x81\n\toperationNameIndexKey byte = 0x82\n\ttagIndexKey           byte = 0x83\n\tdurationIndexKey      byte = 0x84\n\tjsonEncoding          byte = 0x01 // Last 4 bits of the meta byte are for encoding type\n\tprotoEncoding         byte = 0x02 // Last 4 bits of the meta byte are for encoding type\n\tdefaultEncoding       byte = protoEncoding\n)\n\n// SpanWriter for writing spans to badger\ntype SpanWriter struct {\n\tstore        *badger.DB\n\tttl          time.Duration\n\tcache        *CacheStore\n\tencodingType byte\n}\n\n// NewSpanWriter returns a SpawnWriter with cache\nfunc NewSpanWriter(db *badger.DB, c *CacheStore, ttl time.Duration) *SpanWriter {\n\treturn &SpanWriter{\n\t\tstore:        db,\n\t\tttl:          ttl,\n\t\tcache:        c,\n\t\tencodingType: defaultEncoding, // TODO Make configurable\n\t}\n}\n\n// WriteSpan writes the encoded span as well as creates indexes with defined TTL\nfunc (w *SpanWriter) WriteSpan(_ context.Context, span *model.Span) error {\n\t//nolint:gosec // G115\n\texpireTime := uint64(time.Now().Add(w.ttl).Unix())\n\tstartTime := model.TimeAsEpochMicroseconds(span.StartTime)\n\n\t// Avoid doing as much as possible inside the transaction boundary, create entries here\n\tentriesToStore := make([]*badger.Entry, 0, len(span.Tags)+4+len(span.Process.Tags)+len(span.Logs)*4)\n\n\ttrace, err := w.createTraceEntry(span, startTime, expireTime)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tentriesToStore = append(entriesToStore,\n\t\ttrace,\n\t\tw.createBadgerEntry(createIndexKey(serviceNameIndexKey, []byte(span.Process.ServiceName), startTime, span.TraceID), nil, expireTime),\n\t\tw.createBadgerEntry(createIndexKey(operationNameIndexKey, []byte(span.Process.ServiceName+span.OperationName), startTime, span.TraceID), nil, expireTime),\n\t)\n\n\t// It doesn't matter if we overwrite Duration index keys, everything is read at Trace level in any case\n\tdurationValue := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(durationValue, uint64(model.DurationAsMicroseconds(span.Duration)))\n\tentriesToStore = append(entriesToStore, w.createBadgerEntry(createIndexKey(durationIndexKey, durationValue, startTime, span.TraceID), nil, expireTime))\n\n\tfor _, kv := range span.Tags {\n\t\t// Convert everything to string since queries are done that way also\n\t\t// KEY: it<serviceName><tagsKey><traceId> VALUE: <tagsValue>\n\t\tentriesToStore = append(entriesToStore, w.createBadgerEntry(createIndexKey(tagIndexKey, []byte(span.Process.ServiceName+kv.Key+kv.AsString()), startTime, span.TraceID), nil, expireTime))\n\t}\n\n\tfor _, kv := range span.Process.Tags {\n\t\tentriesToStore = append(entriesToStore, w.createBadgerEntry(createIndexKey(tagIndexKey, []byte(span.Process.ServiceName+kv.Key+kv.AsString()), startTime, span.TraceID), nil, expireTime))\n\t}\n\n\tfor _, log := range span.Logs {\n\t\tfor _, kv := range log.Fields {\n\t\t\tentriesToStore = append(entriesToStore, w.createBadgerEntry(createIndexKey(tagIndexKey, []byte(span.Process.ServiceName+kv.Key+kv.AsString()), startTime, span.TraceID), nil, expireTime))\n\t\t}\n\t}\n\n\terr = w.store.Update(func(txn *badger.Txn) error {\n\t\t// Write the entries\n\t\tfor i := range entriesToStore {\n\t\t\terr = txn.SetEntry(entriesToStore[i])\n\t\t\tif err != nil {\n\t\t\t\t// Most likely primary key conflict, but let the caller check this\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// TODO Alternative option is to use simpler keys with the merge value interface.\n\t\t// Requires at least this to be solved: https://github.com/dgraph-io/badger/issues/373\n\n\t\treturn nil\n\t})\n\n\t// Do cache refresh here to release the transaction earlier\n\tw.cache.Update(span.Process.ServiceName, span.OperationName, expireTime)\n\n\treturn err\n}\n\nfunc createIndexKey(indexPrefixKey byte, value []byte, startTime uint64, traceID model.TraceID) []byte {\n\t// KEY: indexKey<indexValue><startTime><traceId> (traceId is last 16 bytes of the key)\n\tkey := make([]byte, 1+len(value)+8+sizeOfTraceID)\n\tkey[0] = (indexPrefixKey & indexKeyRange) | spanKeyPrefix\n\tpos := len(value) + 1\n\tcopy(key[1:pos], value)\n\tbinary.BigEndian.PutUint64(key[pos:], startTime)\n\tpos += 8 // sizeOfTraceID / 2\n\tbinary.BigEndian.PutUint64(key[pos:], traceID.High)\n\tpos += 8 // sizeOfTraceID / 2\n\tbinary.BigEndian.PutUint64(key[pos:], traceID.Low)\n\treturn key\n}\n\nfunc (*SpanWriter) createBadgerEntry(key []byte, value []byte, expireTime uint64) *badger.Entry {\n\treturn &badger.Entry{\n\t\tKey:       key,\n\t\tValue:     value,\n\t\tExpiresAt: expireTime,\n\t}\n}\n\nfunc (w *SpanWriter) createTraceEntry(span *model.Span, startTime, expireTime uint64) (*badger.Entry, error) {\n\tpK, pV, err := createTraceKV(span, w.encodingType, startTime)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te := w.createBadgerEntry(pK, pV, expireTime)\n\te.UserMeta = w.encodingType\n\n\treturn e, nil\n}\n\nfunc createTraceKV(span *model.Span, encodingType byte, startTime uint64) (key []byte, bb []byte, err error) {\n\t// TODO Add Hash for Zipkin compatibility?\n\n\t// Note, KEY must include startTime for proper sorting order for span-ids\n\t// KEY: ti<trace-id><startTime><span-id> VALUE: All the details (json for now) METADATA: Encoding\n\n\tkey = make([]byte, 1+sizeOfTraceID+8+8)\n\tkey[0] = spanKeyPrefix\n\tpos := 1\n\tbinary.BigEndian.PutUint64(key[pos:], span.TraceID.High)\n\tpos += 8\n\tbinary.BigEndian.PutUint64(key[pos:], span.TraceID.Low)\n\tpos += 8\n\tbinary.BigEndian.PutUint64(key[pos:], startTime)\n\tpos += 8\n\tbinary.BigEndian.PutUint64(key[pos:], uint64(span.SpanID))\n\n\tswitch encodingType {\n\tcase protoEncoding:\n\t\tbb, err = proto.Marshal(span)\n\tcase jsonEncoding:\n\t\tbb, err = json.Marshal(span)\n\tdefault:\n\t\treturn nil, nil, fmt.Errorf(\"unknown encoding type: %#02x\", encodingType)\n\t}\n\n\treturn key, bb, err\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/stats.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n//go:build !linux\n\npackage badger\n\nfunc (*Factory) diskStatisticsUpdate() error {\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/stats_linux.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (f *Factory) diskStatisticsUpdate() error {\n\t// These stats are not interesting with Windows as there's no separate tmpfs\n\t// In case of ephemeral these are the same, but we'll report them separately for consistency\n\tvar keyDirStatfs unix.Statfs_t\n\t// Error ignored to satisfy Codecov\n\t_ = unix.Statfs(f.Config.Directories.Keys, &keyDirStatfs)\n\n\tvar valDirStatfs unix.Statfs_t\n\t// Error ignored to satisfy Codecov\n\t_ = unix.Statfs(f.Config.Directories.Values, &valDirStatfs)\n\n\t// Using Bavail instead of Bfree to get non-priviledged user space available\n\t//nolint:gosec // G115\n\tf.metrics.ValueLogSpaceAvailable.Update(int64(valDirStatfs.Bavail) * int64(valDirStatfs.Bsize))\n\t//nolint:gosec // G115\n\tf.metrics.KeyLogSpaceAvailable.Update(int64(keyDirStatfs.Bavail) * int64(keyDirStatfs.Bsize))\n\n\t/*\n\t TODO If we wanted to clean up oldest data to free up diskspace, we need at a minimum an index to the StartTime\n\t Additionally to that, the deletion might not save anything if the ratio of removed values is lower than the RunValueLogGC's deletion ratio\n\t and with the keys the LSM compaction must remove the offending files also. Thus, there's no guarantee the clean up would\n\t actually reduce the amount of diskspace used any faster than allowing TTL to remove them.\n\n\t If badger supports TimeWindow based compaction, then this should be resolved. Not available in 1.5.3\n\t*/\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/stats_linux_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n)\n\nfunc TestDiskStatisticsUpdate(t *testing.T) {\n\tf := NewFactory()\n\tf.Config.Ephemeral = true\n\tf.Config.SyncWrites = false\n\tmFactory := metricstest.NewFactory(0)\n\terr := f.Initialize(mFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\n\terr = f.diskStatisticsUpdate()\n\trequire.NoError(t, err)\n\t_, gs := mFactory.Snapshot()\n\tassert.Positive(t, gs[keyLogSpaceAvailableName])\n\tassert.Positive(t, gs[valueLogSpaceAvailableName])\n}\n"
  },
  {
    "path": "internal/storage/v1/badger/stats_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n//go:build !linux\n\npackage badger\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\nfunc TestDiskStatisticsUpdate(t *testing.T) {\n\tf := NewFactory()\n\tf.Config.Ephemeral = true\n\tf.Config.SyncWrites = false\n\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\n\t// We're not expecting any value in !linux, just no error\n\terr = f.diskStatisticsUpdate()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "internal/storage/v1/blackhole/blackhole.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage blackhole\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// Store is a blackhole. It creates an artificial micro-singularity\n// and forwards all writes to it. We do not know what happens to the\n// data once it reaches the singulatiry, but we know that we cannot\n// get it back.\ntype Store struct {\n\t// nothing, just darkness\n}\n\n// NewStore creates a blackhole store.\nfunc NewStore() *Store {\n\treturn &Store{}\n}\n\n// GetDependencies returns nothing.\nfunc (*Store) GetDependencies(context.Context, time.Time /* endTs */, time.Duration /* lookback */) ([]model.DependencyLink, error) {\n\treturn []model.DependencyLink{}, nil\n}\n\n// WriteSpan writes the given span to blackhole.\nfunc (*Store) WriteSpan(context.Context, *model.Span) error {\n\treturn nil\n}\n\n// GetTrace gets nothing.\nfunc (*Store) GetTrace(context.Context, spanstore.GetTraceParameters) (*model.Trace, error) {\n\treturn nil, spanstore.ErrTraceNotFound\n}\n\n// GetServices returns nothing.\nfunc (*Store) GetServices(context.Context) ([]string, error) {\n\treturn []string{}, nil\n}\n\n// GetOperations returns nothing.\nfunc (*Store) GetOperations(\n\tcontext.Context,\n\tspanstore.OperationQueryParameters,\n) ([]spanstore.Operation, error) {\n\treturn []spanstore.Operation{}, nil\n}\n\n// FindTraces returns nothing.\nfunc (*Store) FindTraces(context.Context, *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\treturn []*model.Trace{}, nil\n}\n\n// FindTraceIDs returns nothing.\nfunc (*Store) FindTraceIDs(context.Context, *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\treturn []model.TraceID{}, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/blackhole/blackhole_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage blackhole\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\nfunc withBlackhole(f func(store *Store)) {\n\tf(NewStore())\n}\n\nfunc TestStoreGetDependencies(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\tlinks, err := store.GetDependencies(context.Background(), time.Now(), time.Hour)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, links)\n\t})\n}\n\nfunc TestStoreWriteSpan(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\terr := store.WriteSpan(context.Background(), nil)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestStoreGetTrace(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\ttrace, err := store.GetTrace(context.Background(), spanstore.GetTraceParameters{TraceID: model.NewTraceID(1, 2)})\n\t\trequire.Error(t, err)\n\t\tassert.Nil(t, trace)\n\t})\n}\n\nfunc TestStoreGetServices(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\tserviceNames, err := store.GetServices(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, serviceNames)\n\t})\n}\n\nfunc TestStoreGetAllOperations(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\toperations, err := store.GetOperations(\n\t\t\tcontext.Background(),\n\t\t\tspanstore.OperationQueryParameters{},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, operations)\n\t})\n}\n\nfunc TestStoreFindTraces(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\ttraces, err := store.FindTraces(context.Background(), nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, traces)\n\t})\n}\n\nfunc TestStoreFindTraceIDs(t *testing.T) {\n\twithBlackhole(func(store *Store) {\n\t\ttraceIDs, err := store.FindTraceIDs(context.Background(), nil)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, traceIDs)\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/blackhole/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage blackhole\n"
  },
  {
    "path": "internal/storage/v1/blackhole/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage blackhole\n"
  },
  {
    "path": "internal/storage/v1/blackhole/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage blackhole\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nFROM cassandra:5.0.6@sha256:5e2c85d2d5db759c28c3efb50905f8d237f958321d6dfd8c176cb148700d9ade\n\nCOPY schema/* /cassandra-schema/\n\nENV CQLSH_HOST=cassandra\n\nRUN groupadd -g 65532 nonroot && \\\n    useradd -u 65532 -g nonroot nonroot --create-home\n\nUSER 65532:65532\nENTRYPOINT [\"/cassandra-schema/docker.sh\"]\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/bootstrap.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2019 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n)\n\n// GetDependencyVersion attempts to determine the version of the dependencies table.\n// TODO: Remove this once we've migrated to V2 permanently. https://github.com/jaegertracing/jaeger/issues/1344\nfunc GetDependencyVersion(s cassandra.Session) Version {\n\tif err := s.Query(\"SELECT ts from dependencies_v2 limit 1;\").Exec(); err != nil {\n\t\treturn V1\n\t}\n\treturn V2\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/bootstrap_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2019 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n)\n\nfunc TestGetDependencyVersionV1(t *testing.T) {\n\tvar (\n\t\tsession = &mocks.Session{}\n\t\tquery   = &mocks.Query{}\n\t)\n\tsession.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(errors.New(\"error\"))\n\n\tassert.Equal(t, V1, GetDependencyVersion(session))\n}\n\nfunc TestGetDependencyVersionV2(t *testing.T) {\n\tvar (\n\t\tsession = &mocks.Session{}\n\t\tquery   = &mocks.Query{}\n\t)\n\tsession.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(nil)\n\tassert.Equal(t, V2, GetDependencyVersion(session))\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/model.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"fmt\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n)\n\n// Dependency is the UDT representation of a Jaeger Dependency.\ntype Dependency struct {\n\tParent    string `cql:\"parent\"`\n\tChild     string `cql:\"child\"`\n\tCallCount int64  `cql:\"call_count\"` // always unsigned, but we cannot explicitly read uint64 from Cassandra\n\tSource    string `cql:\"source\"`\n}\n\n// MarshalUDT handles marshalling a Dependency.\nfunc (d *Dependency) MarshalUDT(name string, info gocql.TypeInfo) ([]byte, error) {\n\tswitch name {\n\tcase \"parent\":\n\t\treturn gocql.Marshal(info, d.Parent)\n\tcase \"child\":\n\t\treturn gocql.Marshal(info, d.Child)\n\tcase \"call_count\":\n\t\treturn gocql.Marshal(info, d.CallCount)\n\tcase \"source\":\n\t\treturn gocql.Marshal(info, d.Source)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// UnmarshalUDT handles unmarshalling a Dependency.\nfunc (d *Dependency) UnmarshalUDT(name string, info gocql.TypeInfo, data []byte) error {\n\tswitch name {\n\tcase \"parent\":\n\t\treturn gocql.Unmarshal(info, data, &d.Parent)\n\tcase \"child\":\n\t\treturn gocql.Unmarshal(info, data, &d.Child)\n\tcase \"call_count\":\n\t\treturn gocql.Unmarshal(info, data, &d.CallCount)\n\tcase \"source\":\n\t\treturn gocql.Unmarshal(info, data, &d.Source)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/model_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"testing\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/gocql/testutils\"\n)\n\nfunc TestDependencyUDT(t *testing.T) {\n\tdependency := &Dependency{\n\t\tParent:    \"bi\",\n\t\tChild:     \"ng\",\n\t\tCallCount: 123,\n\t\tSource:    \"jaeger\",\n\t}\n\n\ttestCase := testutils.UDTTestCase{\n\t\tObj:     dependency,\n\t\tNew:     func() gocql.UDTUnmarshaler { return &Dependency{} },\n\t\tObjName: \"Dependency\",\n\t\tFields: []testutils.UDTField{\n\t\t\t{Name: \"parent\", Type: gocql.TypeAscii, ValIn: []byte(\"bi\"), Err: false},\n\t\t\t{Name: \"child\", Type: gocql.TypeAscii, ValIn: []byte(\"ng\"), Err: false},\n\t\t\t{Name: \"call_count\", Type: gocql.TypeBigInt, ValIn: []byte{0, 0, 0, 0, 0, 0, 0, 123}, Err: false},\n\t\t\t{Name: \"source\", Type: gocql.TypeAscii, ValIn: []byte(\"jaeger\"), Err: false},\n\t\t\t{Name: \"wrong-field\", Err: true},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/storage.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n)\n\n// Version determines which version of the dependencies table to use.\ntype Version int\n\n// IsValid returns true if the Version is a valid one.\nfunc (i Version) IsValid() bool {\n\treturn i >= 0 && i < versionEnumEnd\n}\n\nconst (\n\t// V1 is used when the dependency table is SASI indexed.\n\tV1 Version = iota\n\n\t// V2 is used when the dependency table is NOT SASI indexed.\n\tV2\n\tversionEnumEnd\n\n\tdepsInsertStmtV1 = \"INSERT INTO dependencies(ts, ts_index, dependencies) VALUES (?, ?, ?)\"\n\tdepsInsertStmtV2 = \"INSERT INTO dependencies_v2(ts, ts_bucket, dependencies) VALUES (?, ?, ?)\"\n\tdepsSelectStmtV1 = \"SELECT ts, dependencies FROM dependencies WHERE ts_index >= ? AND ts_index < ?\"\n\tdepsSelectStmtV2 = \"SELECT ts, dependencies FROM dependencies_v2 WHERE ts_bucket IN ? AND ts >= ? AND ts < ?\"\n\n\t// TODO: Make this customizable.\n\ttsBucket = 24 * time.Hour\n)\n\nvar errInvalidVersion = errors.New(\"invalid version\")\n\n// DependencyStore handles all queries and insertions to Cassandra dependencies\ntype DependencyStore struct {\n\tsession                  cassandra.Session\n\tdependenciesTableMetrics *casmetrics.Table\n\tlogger                   *zap.Logger\n\tversion                  Version\n}\n\n// NewDependencyStore returns a DependencyStore\nfunc NewDependencyStore(\n\tsession cassandra.Session,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n\tversion Version,\n) (*DependencyStore, error) {\n\tif !version.IsValid() {\n\t\treturn nil, errInvalidVersion\n\t}\n\treturn &DependencyStore{\n\t\tsession:                  session,\n\t\tdependenciesTableMetrics: casmetrics.NewTable(metricsFactory, \"dependencies\"),\n\t\tlogger:                   logger,\n\t\tversion:                  version,\n\t}, nil\n}\n\n// WriteDependencies implements dependencystore.Writer#WriteDependencies.\nfunc (s *DependencyStore) WriteDependencies(ts time.Time, dependencies []model.DependencyLink) error {\n\tdeps := make([]Dependency, len(dependencies))\n\tfor i, d := range dependencies {\n\t\tdeps[i] = Dependency{\n\t\t\tParent: d.Parent,\n\t\t\tChild:  d.Child,\n\t\t\t//nolint:gosec // G115\n\t\t\tCallCount: int64(d.CallCount),\n\t\t\tSource:    string(d.Source),\n\t\t}\n\t}\n\n\tvar query cassandra.Query\n\tswitch s.version {\n\tcase V1:\n\t\tquery = s.session.Query(depsInsertStmtV1, ts, ts, deps)\n\tcase V2:\n\t\tquery = s.session.Query(depsInsertStmtV2, ts, ts.Truncate(tsBucket), deps)\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported schema version: %v\", s.version)\n\t}\n\treturn s.dependenciesTableMetrics.Exec(query, s.logger)\n}\n\n// GetDependencies returns all interservice dependencies\nfunc (s *DependencyStore) GetDependencies(_ context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error) {\n\tstartTs := endTs.Add(-1 * lookback)\n\tvar query cassandra.Query\n\tswitch s.version {\n\tcase V1:\n\t\tquery = s.session.Query(depsSelectStmtV1, startTs, endTs)\n\tcase V2:\n\t\tquery = s.session.Query(depsSelectStmtV2, getBuckets(startTs, endTs), startTs, endTs)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported schema version: %v\", s.version)\n\t}\n\titer := query.Consistency(cassandra.One).Iter()\n\n\tvar mDependency []model.DependencyLink\n\tvar dependencies []Dependency\n\tvar ts time.Time\n\tfor iter.Scan(&ts, &dependencies) {\n\t\tfor _, dependency := range dependencies {\n\t\t\tdl := model.DependencyLink{\n\t\t\t\tParent: dependency.Parent,\n\t\t\t\tChild:  dependency.Child,\n\t\t\t\t//nolint:gosec // G115\n\t\t\t\tCallCount: uint64(dependency.CallCount),\n\t\t\t\tSource:    dependency.Source,\n\t\t\t}.ApplyDefaults()\n\t\t\tmDependency = append(mDependency, dl)\n\t\t}\n\t}\n\n\tif err := iter.Close(); err != nil {\n\t\ts.logger.Error(\"Failure to read Dependencies\", zap.Time(\"endTs\", endTs), zap.Duration(\"lookback\", lookback), zap.Error(err))\n\t\treturn nil, fmt.Errorf(\"error reading dependencies from storage: %w\", err)\n\t}\n\treturn mDependency, nil\n}\n\nfunc getBuckets(startTs time.Time, endTs time.Time) []time.Time {\n\t// TODO: Preallocate the array using some maths and maybe use a pool? This endpoint probably isn't used enough to warrant this.\n\tvar tsBuckets []time.Time\n\tfor ts := startTs.Truncate(tsBucket); ts.Before(endTs); ts = ts.Add(tsBucket) {\n\t\ttsBuckets = append(tsBuckets, ts)\n\t}\n\treturn tsBuckets\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/dependencystore/storage_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype depStorageTest struct {\n\tsession   *mocks.Session\n\tlogger    *zap.Logger\n\tlogBuffer *testutils.Buffer\n\tstorage   *DependencyStore\n}\n\nfunc withDepStore(version Version, fn func(s *depStorageTest)) {\n\tsession := &mocks.Session{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(time.Second)\n\tdefer metricsFactory.Stop()\n\tstore, _ := NewDependencyStore(session, metricsFactory, logger, version)\n\ts := &depStorageTest{\n\t\tsession:   session,\n\t\tlogger:    logger,\n\t\tlogBuffer: logBuffer,\n\t\tstorage:   store,\n\t}\n\tfn(s)\n}\n\nvar (\n\t_ dependencystore.Reader = &DependencyStore{} // check API conformance\n\t_ dependencystore.Writer = &DependencyStore{} // check API conformance\n)\n\nfunc TestVersionIsValid(t *testing.T) {\n\tassert.True(t, V1.IsValid())\n\tassert.True(t, V2.IsValid())\n\tassert.False(t, versionEnumEnd.IsValid())\n}\n\nfunc TestInvalidVersion(t *testing.T) {\n\t_, err := NewDependencyStore(&mocks.Session{}, metrics.NullFactory, zap.NewNop(), versionEnumEnd)\n\trequire.Error(t, err)\n}\n\nfunc TestDependencyStoreWrite(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption string\n\t\tversion Version\n\t}{\n\t\t{\n\t\t\tcaption: \"V1\",\n\t\t\tversion: V1,\n\t\t},\n\t\t{\n\t\t\tcaption: \"V2\",\n\t\t\tversion: V2,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithDepStore(testCase.version, func(s *depStorageTest) {\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"Exec\").Return(nil)\n\n\t\t\t\tvar args []any\n\t\t\t\tcaptureArgs := mock.MatchedBy(func(v []any) bool {\n\t\t\t\t\targs = v\n\t\t\t\t\treturn true\n\t\t\t\t})\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), captureArgs).Return(query)\n\n\t\t\t\tts := time.Date(2017, time.January, 24, 11, 15, 17, 12345, time.UTC)\n\t\t\t\tdependencies := []model.DependencyLink{\n\t\t\t\t\t{\n\t\t\t\t\t\tParent:    \"a\",\n\t\t\t\t\t\tChild:     \"b\",\n\t\t\t\t\t\tCallCount: 42,\n\t\t\t\t\t\tSource:    model.JaegerDependencyLinkSource,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\terr := s.storage.WriteDependencies(ts, dependencies)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tassert.Len(t, args, 3)\n\t\t\t\tif d, ok := args[0].(time.Time); ok {\n\t\t\t\t\tassert.Equal(t, ts, d)\n\t\t\t\t} else {\n\t\t\t\t\tassert.Fail(t, \"expecting first arg as time.Time\", \"received: %+v\", args)\n\t\t\t\t}\n\t\t\t\tif testCase.version == V2 {\n\t\t\t\t\tif d, ok := args[1].(time.Time); ok {\n\t\t\t\t\t\tassert.Equal(t, time.Date(2017, time.January, 24, 0, 0, 0, 0, time.UTC), d)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.Fail(t, \"expecting second arg as time\", \"received: %+v\", args)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif d, ok := args[1].(time.Time); ok {\n\t\t\t\t\t\tassert.Equal(t, ts, d)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.Fail(t, \"expecting second arg as time.Time\", \"received: %+v\", args)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif d, ok := args[2].([]Dependency); ok {\n\t\t\t\t\tassert.Equal(t, []Dependency{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tParent:    \"a\",\n\t\t\t\t\t\t\tChild:     \"b\",\n\t\t\t\t\t\t\tCallCount: 42,\n\t\t\t\t\t\t\tSource:    \"jaeger\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}, d)\n\t\t\t\t} else {\n\t\t\t\t\tassert.Fail(t, \"expecting third arg as []Dependency\", \"received: %+v\", args)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestDependencyStoreGetDependencies(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption       string\n\t\tqueryError    error\n\t\texpectedError string\n\t\texpectedLogs  []string\n\t\tversion       Version\n\t}{\n\t\t{\n\t\t\tcaption: \"success V1\",\n\t\t\tversion: V1,\n\t\t},\n\t\t{\n\t\t\tcaption: \"success V2\",\n\t\t\tversion: V2,\n\t\t},\n\t\t{\n\t\t\tcaption:       \"failure V1\",\n\t\t\tqueryError:    errors.New(\"query error\"),\n\t\t\texpectedError: \"error reading dependencies from storage: query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failure to read Dependencies\",\n\t\t\t},\n\t\t\tversion: V1,\n\t\t},\n\t\t{\n\t\t\tcaption:       \"failure V2\",\n\t\t\tqueryError:    errors.New(\"query error\"),\n\t\t\texpectedError: \"error reading dependencies from storage: query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failure to read Dependencies\",\n\t\t\t},\n\t\t\tversion: V2,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithDepStore(testCase.version, func(s *depStorageTest) {\n\t\t\t\tscanMatcher := func() any {\n\t\t\t\t\tdeps := [][]Dependency{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{Parent: \"a\", Child: \"b\", CallCount: 1},\n\t\t\t\t\t\t\t{Parent: \"b\", Child: \"c\", CallCount: 1},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{Parent: \"a\", Child: \"b\", CallCount: 1},\n\t\t\t\t\t\t\t{Parent: \"b\", Child: \"c\", CallCount: 1},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\tscanFunc := func(args []any) bool {\n\t\t\t\t\t\tif len(deps) == 0 {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, arg := range args {\n\t\t\t\t\t\t\tif ptr, ok := arg.(*[]Dependency); ok {\n\t\t\t\t\t\t\t\t*ptr = deps[0]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdeps = deps[1:]\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn mock.MatchedBy(scanFunc)\n\t\t\t\t}\n\n\t\t\t\titer := &mocks.Iterator{}\n\t\t\t\titer.On(\"Scan\", scanMatcher()).Return(true)\n\t\t\t\titer.On(\"Scan\", matchEverything()).Return(false)\n\t\t\t\titer.On(\"Close\").Return(testCase.queryError)\n\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"Exec\").Return(nil)\n\t\t\t\tquery.On(\"Consistency\", cassandra.One).Return(query)\n\t\t\t\tquery.On(\"Iter\").Return(iter)\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), matchEverything()).Return(query)\n\n\t\t\t\tdeps, err := s.storage.GetDependencies(context.Background(), time.Now(), 48*time.Hour)\n\n\t\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\texpected := []model.DependencyLink{\n\t\t\t\t\t\t{Parent: \"a\", Child: \"b\", CallCount: 1, Source: model.JaegerDependencyLinkSource},\n\t\t\t\t\t\t{Parent: \"b\", Child: \"c\", CallCount: 1, Source: model.JaegerDependencyLinkSource},\n\t\t\t\t\t\t{Parent: \"a\", Child: \"b\", CallCount: 1, Source: model.JaegerDependencyLinkSource},\n\t\t\t\t\t\t{Parent: \"b\", Child: \"c\", CallCount: 1, Source: model.JaegerDependencyLinkSource},\n\t\t\t\t\t}\n\t\t\t\t\tassert.Equal(t, expected, deps)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\t}\n\t\t\t\tfor _, expectedLog := range testCase.expectedLogs {\n\t\t\t\t\tassert.Contains(t, s.logBuffer.String(), expectedLog, \"Log must contain %s, but was %s\", expectedLog, s.logBuffer.String())\n\t\t\t\t}\n\t\t\t\tif len(testCase.expectedLogs) == 0 {\n\t\t\t\t\tassert.Empty(t, s.logBuffer.String())\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestGetBuckets(t *testing.T) {\n\tvar (\n\t\tstart    = time.Date(2017, time.January, 24, 11, 15, 17, 12345, time.UTC)\n\t\tend      = time.Date(2017, time.January, 26, 11, 15, 17, 12345, time.UTC)\n\t\texpected = []time.Time{\n\t\t\ttime.Date(2017, time.January, 24, 0, 0, 0, 0, time.UTC),\n\t\t\ttime.Date(2017, time.January, 25, 0, 0, 0, 0, time.UTC),\n\t\t\ttime.Date(2017, time.January, 26, 0, 0, 0, 0, time.UTC),\n\t\t}\n\t)\n\tassert.Equal(t, expected, getBuckets(start, end))\n}\n\nfunc matchEverything() any {\n\treturn mock.MatchedBy(func([]any) bool { return true })\n}\n\nfunc TestDependencyStore_UnsupportedVersion(t *testing.T) {\n\tlogger := zap.NewNop()\n\tmetricsFactory := metrics.NullFactory\n\tsession := &mocks.Session{}\n\n\tstore := &DependencyStore{\n\t\tsession:                  session,\n\t\tdependenciesTableMetrics: casmetrics.NewTable(metricsFactory, \"dependencies\"),\n\t\tlogger:                   logger,\n\t\tversion:                  Version(999),\n\t}\n\n\tdeps := []model.DependencyLink{\n\t\t{Parent: \"parent\", Child: \"child\", CallCount: 1},\n\t}\n\n\terr := store.WriteDependencies(time.Now(), deps)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"unsupported schema version\")\n\n\t_, err = store.GetDependencies(context.Background(), time.Now(), time.Hour)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"unsupported schema version\")\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/factory.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/hostname\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\tgocqlw \"github.com/jaegertracing/jaeger/internal/storage/cassandra/gocql\"\n\tcaslock \"github.com/jaegertracing/jaeger/internal/storage/distributedlock/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\tcdepstore \"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/dependencystore\"\n\tcsamplingstore \"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/schema\"\n\tcspanstore \"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n)\n\nvar ( // interface comformance checks\n\t_ storage.Purger               = (*Factory)(nil)\n\t_ storage.SamplingStoreFactory = (*Factory)(nil)\n\t_ io.Closer                    = (*Factory)(nil)\n\t_ storage.ArchiveCapable       = (*Factory)(nil)\n)\n\n// Factory for Cassandra backend.\ntype Factory struct {\n\tOptions *Options\n\n\tmetricsFactory metrics.Factory\n\tlogger         *zap.Logger\n\ttracer         trace.TracerProvider\n\n\tconfig config.Configuration\n\n\tsession cassandra.Session\n\n\t// tests can override this\n\tsessionBuilderFn func(*config.Configuration) (cassandra.Session, error)\n}\n\n// NewFactory creates a new Factory.\nfunc NewFactory() *Factory {\n\treturn &Factory{\n\t\tOptions:          NewOptions(),\n\t\tsessionBuilderFn: NewSession,\n\t}\n}\n\n// InitFromOptions initializes factory from options.\nfunc (f *Factory) ConfigureFromOptions(o *Options) {\n\tf.Options = o\n\tf.config = o.GetConfig()\n}\n\n// Initialize performs internal initialization of the factory.\nfunc (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger, tracer trace.TracerProvider) error {\n\tf.metricsFactory = metricsFactory\n\tf.logger = logger\n\tf.tracer = tracer\n\tsession, err := f.sessionBuilderFn(&f.config)\n\tif err != nil {\n\t\treturn err\n\t}\n\tf.session = session\n\n\treturn nil\n}\n\n// createSession creates session from a configuration\nfunc createSession(c *config.Configuration) (cassandra.Session, error) {\n\tcluster, err := c.NewCluster()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsession, err := cluster.CreateSession()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn gocqlw.WrapCQLSession(session), nil\n}\n\n// newSessionPrerequisites creates tables and types before creating a session\nfunc newSessionPrerequisites(c *config.Configuration) error {\n\tif !c.Schema.CreateSchema {\n\t\treturn nil\n\t}\n\n\tcfg := *c // clone because we need to connect without specifying a keyspace\n\tcfg.Schema.Keyspace = \"\"\n\n\tsession, err := createSession(&cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsc := schema.NewSchemaCreator(session, c.Schema)\n\treturn sc.CreateSchemaIfNotPresent()\n}\n\n// NewSession creates a new Cassandra session\nfunc NewSession(c *config.Configuration) (cassandra.Session, error) {\n\tif err := newSessionPrerequisites(c); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn createSession(c)\n}\n\n// CreateSpanReader implements storage.Factory\nfunc (*Factory) CreateSpanReader() (spanstore.Reader, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// CreateSpanWriter creates a spanstore.Writer.\nfunc (f *Factory) CreateSpanWriter() (spanstore.Writer, error) {\n\toptions, err := writerOptions(f.Options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cspanstore.NewSpanWriter(f.session, f.Options.SpanStoreWriteCacheTTL, f.metricsFactory, f.logger, options...)\n}\n\n// CreateDependencyReader creates a dependencystore.Reader.\nfunc (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) {\n\tversion := cdepstore.GetDependencyVersion(f.session)\n\treturn cdepstore.NewDependencyStore(f.session, f.metricsFactory, f.logger, version)\n}\n\n// CreateLock implements storage.SamplingStoreFactory\nfunc (f *Factory) CreateLock() (distributedlock.Lock, error) {\n\thostId, err := hostname.AsIdentifier()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf.logger.Info(\"Using unique participantName in the distributed lock\", zap.String(\"participantName\", hostId))\n\n\treturn caslock.NewLock(f.session, hostId), nil\n}\n\n// CreateSamplingStore implements storage.SamplingStoreFactory\nfunc (f *Factory) CreateSamplingStore(int /* maxBuckets */) (samplingstore.Store, error) {\n\tsamplingMetricsFactory := f.metricsFactory.Namespace(\n\t\tmetrics.NSOptions{\n\t\t\tTags: map[string]string{\n\t\t\t\t\"role\": \"sampling\",\n\t\t\t},\n\t\t},\n\t)\n\treturn csamplingstore.New(f.session, samplingMetricsFactory, f.logger), nil\n}\n\nfunc writerOptions(opts *Options) ([]cspanstore.Option, error) {\n\tvar tagFilters []dbmodel.TagFilter\n\n\t// drop all tag filters\n\tif !opts.Index.Tags || !opts.Index.ProcessTags || !opts.Index.Logs {\n\t\ttagFilters = append(tagFilters, dbmodel.NewTagFilterDropAll(!opts.Index.Tags, !opts.Index.ProcessTags, !opts.Index.Logs))\n\t}\n\n\t// black/white list tag filters\n\ttagIndexBlacklist := opts.TagIndexBlacklist()\n\ttagIndexWhitelist := opts.TagIndexWhitelist()\n\tif len(tagIndexBlacklist) > 0 && len(tagIndexWhitelist) > 0 {\n\t\treturn nil, errors.New(\"only one of TagIndexBlacklist and TagIndexWhitelist can be specified\")\n\t}\n\tif len(tagIndexBlacklist) > 0 {\n\t\ttagFilters = append(tagFilters, dbmodel.NewBlacklistFilter(tagIndexBlacklist))\n\t} else if len(tagIndexWhitelist) > 0 {\n\t\ttagFilters = append(tagFilters, dbmodel.NewWhitelistFilter(tagIndexWhitelist))\n\t}\n\n\tif len(tagFilters) == 0 {\n\t\treturn nil, nil\n\t} else if len(tagFilters) == 1 {\n\t\treturn []cspanstore.Option{cspanstore.TagFilter(tagFilters[0])}, nil\n\t}\n\n\treturn []cspanstore.Option{cspanstore.TagFilter(dbmodel.NewChainedTagFilter(tagFilters...))}, nil\n}\n\nvar _ io.Closer = (*Factory)(nil)\n\n// Close closes the resources held by the factory\nfunc (f *Factory) Close() error {\n\tif f.session != nil {\n\t\tf.session.Close()\n\t}\n\n\treturn nil\n}\n\nfunc (f *Factory) Purge(_ context.Context) error {\n\treturn f.session.Query(\"TRUNCATE traces\").Exec()\n}\n\nfunc (f *Factory) IsArchiveCapable() bool {\n\treturn f.Options.ArchiveEnabled\n}\n\nfunc (f *Factory) GetSession() cassandra.Session {\n\treturn f.session\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/factory_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n)\n\nfunc TestCreateSpanReaderError(t *testing.T) {\n\tf := NewFactory()\n\t_, err := f.CreateSpanReader()\n\trequire.ErrorContains(t, err, \"not implemented\")\n}\n\nfunc TestConfigureFromOptions(t *testing.T) {\n\tf := NewFactory()\n\to := NewOptions()\n\tf.ConfigureFromOptions(o)\n\tassert.Equal(t, o, f.Options)\n\tassert.Equal(t, o.GetConfig(), f.config)\n}\n\nfunc TestFactory_Purge(t *testing.T) {\n\tf := NewFactory()\n\tvar (\n\t\tsession = &mocks.Session{}\n\t\tquery   = &mocks.Query{}\n\t)\n\tsession.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(nil)\n\tf.session = session\n\n\terr := f.Purge(context.Background())\n\trequire.NoError(t, err)\n\n\tsession.AssertCalled(t, \"Query\", mock.AnythingOfType(\"string\"), mock.Anything)\n\tquery.AssertCalled(t, \"Exec\")\n}\n\nfunc TestNewSessionErrors(t *testing.T) {\n\tt.Run(\"NewCluster error\", func(t *testing.T) {\n\t\tcfg := &config.Configuration{\n\t\t\tConnection: config.Connection{\n\t\t\t\tTLS: configtls.ClientConfig{\n\t\t\t\t\tConfig: configtls.Config{\n\t\t\t\t\t\tCAFile: \"foobar\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\t_, err := NewSession(cfg)\n\t\trequire.ErrorContains(t, err, \"failed to load TLS config\")\n\t})\n\tt.Run(\"CreateSession error\", func(t *testing.T) {\n\t\tcfg := &config.Configuration{}\n\t\t_, err := NewSession(cfg)\n\t\trequire.ErrorContains(t, err, \"no hosts provided\")\n\t})\n\tt.Run(\"CreateSession error with schema\", func(t *testing.T) {\n\t\tcfg := &config.Configuration{\n\t\t\tSchema: config.Schema{\n\t\t\t\tCreateSchema: true,\n\t\t\t},\n\t\t}\n\t\t_, err := NewSession(cfg)\n\t\trequire.ErrorContains(t, err, \"no hosts provided\")\n\t})\n}\n\nfunc TestIsArchiveCapable(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tarchiveEnabled bool\n\t\texpected       bool\n\t}{\n\t\t{\n\t\t\tname:           \"archive capable\",\n\t\t\tarchiveEnabled: true,\n\t\t\texpected:       true,\n\t\t},\n\t\t{\n\t\t\tname:           \"not capable\",\n\t\t\tarchiveEnabled: false,\n\t\t\texpected:       false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tfactory := &Factory{\n\t\t\t\tOptions: &Options{\n\t\t\t\t\tArchiveEnabled: test.archiveEnabled,\n\t\t\t\t},\n\t\t\t}\n\t\t\tresult := factory.IsArchiveCapable()\n\t\t\trequire.Equal(t, test.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/helper.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n)\n\ntype mockSessionBuilder struct {\n\tindex    int\n\tsessions []*mocks.Session\n\terrors   []error\n}\n\nfunc (m *mockSessionBuilder) add(session *mocks.Session, err error) *mockSessionBuilder {\n\tm.sessions = append(m.sessions, session)\n\tm.errors = append(m.errors, err)\n\treturn m\n}\n\nfunc (m *mockSessionBuilder) build(*config.Configuration) (cassandra.Session, error) {\n\tsession := m.sessions[m.index]\n\terr := m.errors[m.index]\n\tm.index++\n\treturn session, err\n}\n\nfunc MockSession(f *Factory, session *mocks.Session, err error) {\n\tf.sessionBuilderFn = new(mockSessionBuilder).add(session, err).build\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/options.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n)\n\n// Options contains various type of Cassandra configs and provides the ability\n// to bind them to command line flag and apply overlays, so that some configurations\n// (e.g. archive) may be underspecified and infer the rest of its parameters from primary.\ntype Options struct {\n\tconfig.Configuration   `mapstructure:\",squash\"`\n\tSpanStoreWriteCacheTTL time.Duration `mapstructure:\"span_store_write_cache_ttl\"`\n\tIndex                  IndexConfig   `mapstructure:\"index\"`\n\tArchiveEnabled         bool          `mapstructure:\"-\"`\n}\n\n// IndexConfig configures indexing.\n// By default all indexing is enabled.\ntype IndexConfig struct {\n\tLogs         bool   `mapstructure:\"logs\"`\n\tTags         bool   `mapstructure:\"tags\"`\n\tProcessTags  bool   `mapstructure:\"process_tags\"`\n\tTagBlackList string `mapstructure:\"tag_blacklist\"`\n\tTagWhiteList string `mapstructure:\"tag_whitelist\"`\n}\n\n// NewOptions creates a new Options struct.\nfunc NewOptions() *Options {\n\t// TODO all default values should be defined via cobra flags\n\toptions := &Options{\n\t\tConfiguration:          config.DefaultConfiguration(),\n\t\tSpanStoreWriteCacheTTL: time.Hour * 12,\n\t\tArchiveEnabled:         false,\n\t}\n\n\treturn options\n}\n\nfunc (opt *Options) GetConfig() config.Configuration {\n\treturn opt.Configuration\n}\n\n// TagIndexBlacklist returns the list of blacklisted tags\nfunc (opt *Options) TagIndexBlacklist() []string {\n\tif opt.Index.TagBlackList != \"\" {\n\t\treturn strings.Split(opt.Index.TagBlackList, \",\")\n\t}\n\n\treturn nil\n}\n\n// TagIndexWhitelist returns the list of whitelisted tags\nfunc (opt *Options) TagIndexWhitelist() []string {\n\tif opt.Index.TagWhiteList != \"\" {\n\t\treturn strings.Split(opt.Index.TagWhiteList, \",\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/options_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestOptions(t *testing.T) {\n\topts := NewOptions()\n\tprimary := opts.GetConfig()\n\tassert.NotEmpty(t, primary.Schema.Keyspace)\n\tassert.NotEmpty(t, primary.Connection.Servers)\n\tassert.Equal(t, 2, primary.Connection.ConnectionsPerHost)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/samplingstore/storage.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nconst (\n\tbuckets        = `(0,1,2,3,4,5,6,7,8,9)`\n\tconstBucket    = 1\n\tconstBucketStr = `1`\n\n\tinsertThroughput       = `INSERT INTO operation_throughput(bucket, ts, throughput) VALUES (?, ?, ?)`\n\tgetThroughput          = `SELECT throughput FROM operation_throughput WHERE bucket IN ` + buckets + ` AND ts > ? AND ts <= ?`\n\tinsertProbabilities    = `INSERT INTO sampling_probabilities(bucket, ts, hostname, probabilities) VALUES (?, ?, ?, ?)`\n\tgetLatestProbabilities = `SELECT probabilities FROM sampling_probabilities WHERE bucket = ` + constBucketStr + ` LIMIT 1`\n)\n\n// probabilityAndQPS contains the sampling probability and measured qps for an operation.\ntype probabilityAndQPS struct {\n\tProbability float64\n\tQPS         float64\n}\n\n// serviceOperationData contains the sampling probabilities and measured qps for all operations in a service.\n// ie [service][operation] = ProbabilityAndQPS\ntype serviceOperationData map[string]map[string]*probabilityAndQPS\n\ntype samplingStoreMetrics struct {\n\toperationThroughput *casmetrics.Table\n\tprobabilities       *casmetrics.Table\n}\n\n// SamplingStore handles all insertions and queries for sampling data to and from Cassandra\ntype SamplingStore struct {\n\tsession cassandra.Session\n\tmetrics samplingStoreMetrics\n\tlogger  *zap.Logger\n}\n\n// New creates a new cassandra sampling store.\nfunc New(session cassandra.Session, factory metrics.Factory, logger *zap.Logger) *SamplingStore {\n\treturn &SamplingStore{\n\t\tsession: session,\n\t\tmetrics: samplingStoreMetrics{\n\t\t\toperationThroughput: casmetrics.NewTable(factory, \"operation_throughput\"),\n\t\t\tprobabilities:       casmetrics.NewTable(factory, \"probabilities\"),\n\t\t},\n\t\tlogger: logger,\n\t}\n}\n\n// InsertThroughput implements samplingstore.Writer#InsertThroughput.\nfunc (s *SamplingStore) InsertThroughput(throughput []*model.Throughput) error {\n\tthroughputStr := throughputToString(throughput)\n\tquery := s.session.Query(insertThroughput, generateRandomBucket(), gocql.TimeUUID(), throughputStr)\n\treturn s.metrics.operationThroughput.Exec(query, s.logger)\n}\n\n// GetThroughput implements samplingstore.Reader#GetThroughput.\nfunc (s *SamplingStore) GetThroughput(start, end time.Time) ([]*model.Throughput, error) {\n\titer := s.session.Query(getThroughput, gocql.UUIDFromTime(start), gocql.UUIDFromTime(end)).Iter()\n\tvar throughput []*model.Throughput\n\tvar throughputStr string\n\tfor iter.Scan(&throughputStr) {\n\t\tthroughput = append(throughput, s.stringToThroughput(throughputStr)...)\n\t}\n\tif err := iter.Close(); err != nil {\n\t\terr = fmt.Errorf(\"error reading throughput from storage: %w\", err)\n\t\treturn nil, err\n\t}\n\treturn throughput, nil\n}\n\n// InsertProbabilitiesAndQPS implements samplingstore.Writer#InsertProbabilitiesAndQPS.\nfunc (s *SamplingStore) InsertProbabilitiesAndQPS(\n\thostname string,\n\tprobabilities model.ServiceOperationProbabilities,\n\tqps model.ServiceOperationQPS,\n) error {\n\tprobabilitiesAndQPSStr := probabilitiesAndQPSToString(probabilities, qps)\n\tquery := s.session.Query(insertProbabilities, constBucket, gocql.TimeUUID(), hostname, probabilitiesAndQPSStr)\n\treturn s.metrics.probabilities.Exec(query, s.logger)\n}\n\n// GetLatestProbabilities implements samplingstore.Reader#GetLatestProbabilities.\nfunc (s *SamplingStore) GetLatestProbabilities() (model.ServiceOperationProbabilities, error) {\n\titer := s.session.Query(getLatestProbabilities).Iter()\n\tvar probabilitiesStr string\n\titer.Scan(&probabilitiesStr)\n\tif err := iter.Close(); err != nil {\n\t\terr = fmt.Errorf(\"error reading probabilities from storage: %w\", err)\n\t\treturn nil, err\n\t}\n\treturn s.stringToProbabilities(probabilitiesStr), nil\n}\n\n// This is random enough for storage purposes\nfunc generateRandomBucket() int64 {\n\treturn time.Now().UnixNano() % 10\n}\n\nfunc probabilitiesAndQPSToString(probabilities model.ServiceOperationProbabilities, qps model.ServiceOperationQPS) string {\n\tvar buf bytes.Buffer\n\twriter := csv.NewWriter(&buf)\n\tfor svc, opProbabilities := range probabilities {\n\t\tfor op, probability := range opProbabilities {\n\t\t\topQPS := 0.0\n\t\t\tif _, ok := qps[svc]; ok {\n\t\t\t\topQPS = qps[svc][op]\n\t\t\t}\n\t\t\twriter.Write([]string{\n\t\t\t\tsvc, op, strconv.FormatFloat(probability, 'f', -1, 64),\n\t\t\t\tstrconv.FormatFloat(opQPS, 'f', -1, 64),\n\t\t\t})\n\t\t}\n\t}\n\twriter.Flush()\n\treturn buf.String()\n}\n\nfunc (s *SamplingStore) stringToProbabilitiesAndQPS(probabilitiesAndQPSStr string) serviceOperationData {\n\tprobabilitiesAndQPS := make(serviceOperationData)\n\tappendFunc := s.appendProbabilityAndQPS(probabilitiesAndQPS)\n\ts.parseString(probabilitiesAndQPSStr, 4, appendFunc)\n\treturn probabilitiesAndQPS\n}\n\nfunc (s *SamplingStore) stringToProbabilities(probabilitiesStr string) model.ServiceOperationProbabilities {\n\tprobabilities := make(model.ServiceOperationProbabilities)\n\tappendFunc := s.appendProbability(probabilities)\n\ts.parseString(probabilitiesStr, 4, appendFunc)\n\treturn probabilities\n}\n\nfunc throughputToString(throughput []*model.Throughput) string {\n\tvar buf bytes.Buffer\n\twriter := csv.NewWriter(&buf)\n\tfor _, t := range throughput {\n\t\twriter.Write([]string{t.Service, t.Operation, strconv.Itoa(int(t.Count)), probabilitiesSetToString(t.Probabilities)})\n\t}\n\twriter.Flush()\n\treturn buf.String()\n}\n\nfunc probabilitiesSetToString(probabilities map[string]struct{}) string {\n\tvar buf bytes.Buffer\n\tfor probability := range probabilities {\n\t\tbuf.WriteString(probability)\n\t\tbuf.WriteString(\",\")\n\t}\n\treturn strings.TrimSuffix(buf.String(), \",\")\n}\n\nfunc (s *SamplingStore) stringToThroughput(throughputStr string) []*model.Throughput {\n\tvar throughput []*model.Throughput\n\tappendFunc := s.appendThroughput(&throughput)\n\ts.parseString(throughputStr, 4, appendFunc)\n\treturn throughput\n}\n\nfunc (s *SamplingStore) appendProbabilityAndQPS(svcOpData serviceOperationData) func(csvFields []string) {\n\treturn func(csvFields []string) {\n\t\tprobability, err := strconv.ParseFloat(csvFields[2], 64)\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"probability cannot be parsed\", zap.Any(\"entries\", csvFields), zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\tqps, err := strconv.ParseFloat(csvFields[3], 64)\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"qps cannot be parsed\", zap.Any(\"entries\", csvFields), zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\tservice := csvFields[0]\n\t\toperation := csvFields[1]\n\t\tif _, ok := svcOpData[service]; !ok {\n\t\t\tsvcOpData[service] = make(map[string]*probabilityAndQPS)\n\t\t}\n\t\tsvcOpData[service][operation] = &probabilityAndQPS{\n\t\t\tProbability: probability,\n\t\t\tQPS:         qps,\n\t\t}\n\t}\n}\n\nfunc (s *SamplingStore) appendProbability(probabilities model.ServiceOperationProbabilities) func(csvFields []string) {\n\treturn func(csvFields []string) {\n\t\tprobability, err := strconv.ParseFloat(csvFields[2], 64)\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"probability cannot be parsed\", zap.Any(\"entries\", csvFields), zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\tservice := csvFields[0]\n\t\toperation := csvFields[1]\n\t\tif _, ok := probabilities[service]; !ok {\n\t\t\tprobabilities[service] = make(map[string]float64)\n\t\t}\n\t\tprobabilities[service][operation] = probability\n\t}\n}\n\nfunc (s *SamplingStore) appendThroughput(throughput *[]*model.Throughput) func(csvFields []string) {\n\treturn func(csvFields []string) {\n\t\tcount, err := strconv.Atoi(csvFields[2])\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"throughput count cannot be parsed\", zap.Any(\"entries\", csvFields), zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\t*throughput = append(*throughput, &model.Throughput{\n\t\t\tService:       csvFields[0],\n\t\t\tOperation:     csvFields[1],\n\t\t\tCount:         int64(count),\n\t\t\tProbabilities: parseProbabilitiesSet(csvFields[3]),\n\t\t})\n\t}\n}\n\nfunc parseProbabilitiesSet(probabilitiesStr string) map[string]struct{} {\n\tret := map[string]struct{}{}\n\tfor probability := range strings.SplitSeq(probabilitiesStr, \",\") {\n\t\tif probability != \"\" {\n\t\t\tret[probability] = struct{}{}\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc (s *SamplingStore) parseString(str string, numColumns int, appendFunc func(csvFields []string)) {\n\treader := csv.NewReader(strings.NewReader(str))\n\tfor {\n\t\tcsvFields, err := reader.Read()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"failed to read csv\", zap.Error(err))\n\t\t}\n\t\tif len(csvFields) != numColumns {\n\t\t\ts.logger.Warn(\"incomplete throughput data\", zap.Int(\"expected_columns\", numColumns), zap.Any(\"entries\", csvFields))\n\t\t\tcontinue\n\t\t}\n\t\tappendFunc(csvFields)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/samplingstore/storage_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nvar testTime = time.Date(2017, time.January, 24, 11, 15, 17, 12345, time.UTC)\n\ntype samplingStoreTest struct {\n\tsession   *mocks.Session\n\tlogger    *zap.Logger\n\tlogBuffer *testutils.Buffer\n\tstore     *SamplingStore\n}\n\nfunc withSamplingStore(fn func(r *samplingStoreTest)) {\n\tsession := &mocks.Session{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\tr := &samplingStoreTest{\n\t\tsession:   session,\n\t\tlogger:    logger,\n\t\tlogBuffer: logBuffer,\n\t\tstore:     New(session, metricsFactory, logger),\n\t}\n\tfn(r)\n}\n\nvar _ samplingstore.Store = &SamplingStore{} // check API conformance\n\nfunc TestInsertThroughput(t *testing.T) {\n\twithSamplingStore(func(s *samplingStoreTest) {\n\t\tquery := &mocks.Query{}\n\t\tquery.On(\"Exec\").Return(nil)\n\n\t\tvar args []any\n\t\tcaptureArgs := mock.MatchedBy(func(v []any) bool {\n\t\t\targs = v\n\t\t\treturn true\n\t\t})\n\n\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), captureArgs).Return(query)\n\n\t\tthroughput := []*model.Throughput{\n\t\t\t{\n\t\t\t\tService:   \"svc,withcomma\",\n\t\t\t\tOperation: \"op,withcomma\",\n\t\t\t\tCount:     40,\n\t\t\t},\n\t\t}\n\t\terr := s.store.InsertThroughput(throughput)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, args, 3)\n\t\tif _, ok := args[0].(int64); !ok {\n\t\t\tassert.Fail(t, \"expecting first arg as int64\", \"received: %+v\", args)\n\t\t}\n\t\tif _, ok := args[1].(gocql.UUID); !ok {\n\t\t\tassert.Fail(t, \"expecting second arg as gocql.UUID\", \"received: %+v\", args)\n\t\t}\n\t\tif d, ok := args[2].(string); ok {\n\t\t\tassert.Equal(t, \"\\\"svc,withcomma\\\",\\\"op,withcomma\\\",40,\\n\", d)\n\t\t} else {\n\t\t\tassert.Fail(t, \"expecting third arg as string\", \"received: %+v\", args)\n\t\t}\n\t})\n}\n\nfunc TestInsertProbabilitiesAndQPS(t *testing.T) {\n\twithSamplingStore(func(s *samplingStoreTest) {\n\t\tquery := &mocks.Query{}\n\t\tquery.On(\"Exec\").Return(nil)\n\n\t\tvar args []any\n\t\tcaptureArgs := mock.MatchedBy(func(v []any) bool {\n\t\t\targs = v\n\t\t\treturn true\n\t\t})\n\n\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), captureArgs).Return(query)\n\n\t\thostname := \"hostname\"\n\t\tprobabilities := model.ServiceOperationProbabilities{\n\t\t\t\"svc\": map[string]float64{\n\t\t\t\t\"op\": 0.84,\n\t\t\t},\n\t\t}\n\t\tqps := model.ServiceOperationQPS{\n\t\t\t\"svc\": map[string]float64{\n\t\t\t\t\"op\": 40,\n\t\t\t},\n\t\t}\n\n\t\terr := s.store.InsertProbabilitiesAndQPS(hostname, probabilities, qps)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Len(t, args, 4)\n\t\tif d, ok := args[0].(int); ok {\n\t\t\tassert.Equal(t, 1, d)\n\t\t} else {\n\t\t\tassert.Fail(t, \"expecting first arg as int\", \"received: %+v\", args)\n\t\t}\n\t\tif _, ok := args[1].(gocql.UUID); !ok {\n\t\t\tassert.Fail(t, \"expecting second arg as gocql.UUID\", \"received: %+v\", args)\n\t\t}\n\t\tif d, ok := args[2].(string); ok {\n\t\t\tassert.Equal(t, hostname, d)\n\t\t} else {\n\t\t\tassert.Fail(t, \"expecting third arg as string\", \"received: %+v\", args)\n\t\t}\n\t\tif d, ok := args[3].(string); ok {\n\t\t\tassert.Equal(t, \"svc,op,0.84,40\\n\", d)\n\t\t} else {\n\t\t\tassert.Fail(t, \"expecting fourth arg as string\", \"received: %+v\", args)\n\t\t}\n\t})\n}\n\nfunc TestGetThroughput(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption       string\n\t\tqueryError    error\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tcaption: \"success\",\n\t\t},\n\t\t{\n\t\t\tcaption:       \"failure\",\n\t\t\tqueryError:    errors.New(\"query error\"),\n\t\t\texpectedError: \"error reading throughput from storage: query error\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithSamplingStore(func(s *samplingStoreTest) {\n\t\t\t\tscanMatcher := func() any {\n\t\t\t\t\tthroughputStr := []string{\n\t\t\t\t\t\t\"\\\"svc,withcomma\\\",\\\"op,withcomma\\\",40,\\\"0.1,\\\"\\n\",\n\t\t\t\t\t\t\"svc,op,50,\\n\",\n\t\t\t\t\t}\n\t\t\t\t\tscanFunc := func(args []any) bool {\n\t\t\t\t\t\tif len(throughputStr) == 0 {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, arg := range args {\n\t\t\t\t\t\t\tif ptr, ok := arg.(*string); ok {\n\t\t\t\t\t\t\t\t*ptr = throughputStr[0]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthroughputStr = throughputStr[1:]\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn mock.MatchedBy(scanFunc)\n\t\t\t\t}\n\n\t\t\t\titer := &mocks.Iterator{}\n\t\t\t\titer.On(\"Scan\", scanMatcher()).Return(true)\n\t\t\t\titer.On(\"Scan\", matchEverything()).Return(false)\n\t\t\t\titer.On(\"Close\").Return(testCase.queryError)\n\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"Iter\").Return(iter)\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), matchEverything()).Return(query)\n\n\t\t\t\tthroughput, err := s.store.GetThroughput(testTime, testTime)\n\n\t\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.Len(t, throughput, 2)\n\t\t\t\t\tassert.Equal(t,\n\t\t\t\t\t\tmodel.Throughput{\n\t\t\t\t\t\t\tService:       \"svc,withcomma\",\n\t\t\t\t\t\t\tOperation:     \"op,withcomma\",\n\t\t\t\t\t\t\tCount:         40,\n\t\t\t\t\t\t\tProbabilities: map[string]struct{}{\"0.1\": {}},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t*throughput[0],\n\t\t\t\t\t)\n\t\t\t\t\tassert.Equal(t,\n\t\t\t\t\t\tmodel.Throughput{\n\t\t\t\t\t\t\tService:       \"svc\",\n\t\t\t\t\t\t\tOperation:     \"op\",\n\t\t\t\t\t\t\tCount:         50,\n\t\t\t\t\t\t\tProbabilities: map[string]struct{}{},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t*throughput[1],\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestGetLatestProbabilities(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption       string\n\t\tqueryError    error\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tcaption: \"success\",\n\t\t},\n\t\t{\n\t\t\tcaption:       \"failure\",\n\t\t\tqueryError:    errors.New(\"query error\"),\n\t\t\texpectedError: \"error reading probabilities from storage: query error\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithSamplingStore(func(s *samplingStoreTest) {\n\t\t\t\tscanMatcher := func() any {\n\t\t\t\t\tprobabilitiesStr := []string{\n\t\t\t\t\t\t\"svc,op,0.84,40\\n\",\n\t\t\t\t\t}\n\t\t\t\t\tscanFunc := func(args []any) bool {\n\t\t\t\t\t\tif len(probabilitiesStr) == 0 {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, arg := range args {\n\t\t\t\t\t\t\tif ptr, ok := arg.(*string); ok {\n\t\t\t\t\t\t\t\t*ptr = probabilitiesStr[0]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprobabilitiesStr = probabilitiesStr[1:]\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn mock.MatchedBy(scanFunc)\n\t\t\t\t}\n\n\t\t\t\titer := &mocks.Iterator{}\n\t\t\t\titer.On(\"Scan\", scanMatcher()).Return(true)\n\t\t\t\titer.On(\"Scan\", scanMatcher()).Return(false)\n\t\t\t\titer.On(\"Close\").Return(testCase.queryError)\n\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"Iter\").Return(iter)\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\")).Return(query)\n\n\t\t\t\tprobabilities, err := s.store.GetLatestProbabilities()\n\n\t\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.InDelta(t, 0.84, probabilities[\"svc\"][\"op\"], 0.01)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc matchEverything() any {\n\treturn mock.MatchedBy(func(_ []any) bool { return true })\n}\n\nfunc TestGenerateRandomBucket(t *testing.T) {\n\tassert.Contains(t, []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, generateRandomBucket())\n}\n\nfunc TestThroughputToString(t *testing.T) {\n\tthroughput := []*model.Throughput{\n\t\t{Service: \"svc1\", Operation: \"op,1\", Count: 1, Probabilities: map[string]struct{}{\"1\": {}}},\n\t\t{Service: \"svc2\", Operation: \"op2\", Count: 2, Probabilities: map[string]struct{}{}},\n\t}\n\tstr := throughputToString(throughput)\n\tassert.True(t, str == \"svc1,\\\"op,1\\\",1,1\\nsvc2,op2,2,\\n\" || str == \"svc2,op2,2,\\nsvc1,1\\\"op,1\\\",1,1\\n\")\n\n\tthroughput = []*model.Throughput{\n\t\t{Service: \"svc1\", Operation: \"op,1\", Count: 1, Probabilities: map[string]struct{}{\"1\": {}, \"2\": {}}},\n\t}\n\tstr = throughputToString(throughput)\n\tassert.True(t, str == \"svc1,\\\"op,1\\\",1,\\\"1,2\\\"\\n\" || str == \"svc1,\\\"op,1\\\",1,\\\"2,1\\\"\\n\")\n}\n\nfunc TestStringToThroughput(t *testing.T) {\n\ts := &SamplingStore{logger: zap.NewNop()}\n\ttestStr := \"svc1,\\\"op,1\\\",1,\\\"0.1,0.2\\\"\\nsvc2,op2,2,\\nsvc3,op3,string,\\nsvc4,op4\\nsvc5\\n\"\n\tthroughput := s.stringToThroughput(testStr)\n\n\tassert.Len(t, throughput, 2)\n\tassert.Equal(t,\n\t\tmodel.Throughput{\n\t\t\tService:       \"svc1\",\n\t\t\tOperation:     \"op,1\",\n\t\t\tCount:         1,\n\t\t\tProbabilities: map[string]struct{}{\"0.1\": {}, \"0.2\": {}},\n\t\t},\n\t\t*throughput[0],\n\t)\n\tassert.Equal(t,\n\t\tmodel.Throughput{\n\t\t\tService:       \"svc2\",\n\t\t\tOperation:     \"op2\",\n\t\t\tCount:         2,\n\t\t\tProbabilities: map[string]struct{}{},\n\t\t},\n\t\t*throughput[1],\n\t)\n}\n\nfunc TestProbabilitiesAndQPSToString(t *testing.T) {\n\tprobabilities := model.ServiceOperationProbabilities{\n\t\t\"svc,1\": map[string]float64{\n\t\t\thttp.MethodGet: 0.001,\n\t\t},\n\t}\n\tqps := model.ServiceOperationQPS{\n\t\t\"svc,1\": map[string]float64{\n\t\t\thttp.MethodGet: 62.3,\n\t\t},\n\t}\n\tstr := probabilitiesAndQPSToString(probabilities, qps)\n\tassert.Equal(t, \"\\\"svc,1\\\",GET,0.001,62.3\\n\", str)\n}\n\nfunc TestStringToProbabilitiesAndQPS(t *testing.T) {\n\ts := &SamplingStore{logger: zap.NewNop()}\n\ttestStr := \"svc1,GET,0.001,63.2\\nsvc1,PUT,0.002,0.0\\nsvc2,GET,0.5,34.2\\nsvc2\\nsvc2,PUT,string,22.2\\nsvc2,DELETE,0.3,string\\n\"\n\tprobabilities := s.stringToProbabilitiesAndQPS(testStr)\n\n\tassert.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]*probabilityAndQPS{\n\t\thttp.MethodGet: {\n\t\t\tProbability: 0.001,\n\t\t\tQPS:         63.2,\n\t\t},\n\t\thttp.MethodPut: {\n\t\t\tProbability: 0.002,\n\t\t\tQPS:         0.0,\n\t\t},\n\t}, probabilities[\"svc1\"])\n\tassert.Equal(t, map[string]*probabilityAndQPS{\n\t\thttp.MethodGet: {\n\t\t\tProbability: 0.5,\n\t\t\tQPS:         34.2,\n\t\t},\n\t}, probabilities[\"svc2\"])\n}\n\nfunc TestStringToProbabilities(t *testing.T) {\n\ts := &SamplingStore{logger: zap.NewNop()}\n\ttestStr := \"svc1,GET,0.001,63.2\\nsvc1,PUT,0.002,0.0\\nsvc2,GET,0.5,34.2\\nsvc2\\nsvc2,PUT,string,34.2\\n\"\n\tprobabilities := s.stringToProbabilities(testStr)\n\n\tassert.Len(t, probabilities, 2)\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.001, http.MethodPut: 0.002}, probabilities[\"svc1\"])\n\tassert.Equal(t, map[string]float64{http.MethodGet: 0.5}, probabilities[\"svc2\"])\n}\n\nfunc TestProbabilitiesSetToString(t *testing.T) {\n\ts := probabilitiesSetToString(map[string]struct{}{\"0.000001\": {}, \"0.000002\": {}})\n\tassert.True(t, s == \"0.000001,0.000002\" || s == \"0.000002,0.000001\")\n\tassert.Empty(t, probabilitiesSetToString(nil))\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/savetracetest/main.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jtracer\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tcascfg \"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra\"\n\tcspanstore \"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore\"\n)\n\nvar logger, _ = zap.NewDevelopment()\n\n// TODO: this is going morph into a load testing framework for cassandra 3.7\nfunc main() {\n\tnoScope := metrics.NullFactory\n\tcConfig := &cascfg.Configuration{\n\t\tSchema: cascfg.Schema{\n\t\t\tKeyspace: \"jaeger_v1_test\",\n\t\t},\n\t\tConnection: cascfg.Connection{\n\t\t\tServers:            []string{\"127.0.0.1\"},\n\t\t\tConnectionsPerHost: 10,\n\t\t\tProtoVersion:       4,\n\t\t},\n\t\tQuery: cascfg.Query{\n\t\t\tTimeout: time.Millisecond * 750,\n\t\t},\n\t}\n\tcqlSession, err := cassandra.NewSession(cConfig)\n\tif err != nil {\n\t\tlogger.Fatal(\"Cannot create Cassandra session\", zap.Error(err))\n\t}\n\ttracerProvider, tracerCloser, err := jtracer.NewProvider(context.Background(), \"savetracetest\")\n\tif err != nil {\n\t\tlogger.Fatal(\"Failed to initialize tracer\", zap.Error(err))\n\t}\n\tdefer tracerCloser(context.Background())\n\tspanStore, err := cspanstore.NewSpanWriter(cqlSession, time.Hour*12, noScope, logger)\n\tif err != nil {\n\t\tlogger.Fatal(\"Failed to create span writer\", zap.Error(err))\n\t}\n\tspanReader, err := cspanstore.NewSpanReader(cqlSession, noScope, logger, tracerProvider.Tracer(\"cspanstore.SpanReader\"))\n\tif err != nil {\n\t\tlogger.Fatal(\"Failed to create span reader\", zap.Error(err))\n\t}\n\tctx := context.Background()\n\tif err = spanStore.WriteSpan(ctx, getSomeSpan()); err != nil {\n\t\tlogger.Fatal(\"Failed to save\", zap.Error(err))\n\t} else {\n\t\tlogger.Info(\"Saved span\", zap.String(\"spanID\", getSomeSpan().SpanID.String()))\n\t}\n\ts := getSomeSpan()\n\ttrace, err := spanReader.GetTrace(ctx, spanstore.GetTraceParameters{TraceID: s.TraceID})\n\tif err != nil {\n\t\tlogger.Fatal(\"Failed to read\", zap.Error(err))\n\t} else {\n\t\tlogger.Info(\"Loaded trace\", zap.Any(\"trace\", trace))\n\t}\n\n\ttqp := &spanstore.TraceQueryParameters{\n\t\tServiceName:  \"someServiceName\",\n\t\tStartTimeMin: time.Now().Add(time.Hour * -1),\n\t\tStartTimeMax: time.Now().Add(time.Hour),\n\t}\n\tlogger.Info(\"Check main query\")\n\tqueryAndPrint(ctx, spanReader, tqp)\n\n\ttqp.OperationName = \"opName\"\n\tlogger.Info(\"Check query with operation\")\n\tqueryAndPrint(ctx, spanReader, tqp)\n\n\ttqp.Tags = map[string]string{\n\t\t\"someKey\": \"someVal\",\n\t}\n\tlogger.Info(\"Check query with operation name and tags\")\n\tqueryAndPrint(ctx, spanReader, tqp)\n\n\ttqp.DurationMin = 0\n\ttqp.DurationMax = time.Hour\n\ttqp.Tags = map[string]string{}\n\tlogger.Info(\"check query with duration\")\n\tqueryAndPrint(ctx, spanReader, tqp)\n}\n\nfunc queryAndPrint(ctx context.Context, spanReader *cspanstore.SpanReader, tqp *spanstore.TraceQueryParameters) {\n\ttraces, err := spanReader.FindTraces(ctx, tqp)\n\tif err != nil {\n\t\tlogger.Fatal(\"Failed to query\", zap.Error(err))\n\t} else {\n\t\tlogger.Info(\"Found trace(s)\", zap.Any(\"traces\", traces))\n\t}\n}\n\nfunc getSomeProcess() *model.Process {\n\tprocessTagVal := \"indexMe\"\n\treturn &model.Process{\n\t\tServiceName: \"someServiceName\",\n\t\tTags: model.KeyValues{\n\t\t\tmodel.String(\"processTagKey\", processTagVal),\n\t\t},\n\t}\n}\n\nfunc getSomeSpan() *model.Span {\n\ttraceID := model.NewTraceID(1, 2)\n\treturn &model.Span{\n\t\tTraceID:       traceID,\n\t\tSpanID:        model.NewSpanID(3),\n\t\tOperationName: \"opName\",\n\t\tReferences:    model.MaybeAddParentSpanID(traceID, 4, getReferences()),\n\t\tFlags:         model.Flags(uint32(5)),\n\t\tStartTime:     time.Now(),\n\t\tDuration:      50000 * time.Microsecond,\n\t\tTags:          getTags(),\n\t\tLogs:          getLogs(),\n\t\tProcess:       getSomeProcess(),\n\t}\n}\n\nfunc getReferences() []model.SpanRef {\n\treturn []model.SpanRef{\n\t\t{\n\t\t\tRefType: model.ChildOf,\n\t\t\tTraceID: model.NewTraceID(1, 1),\n\t\t\tSpanID:  model.NewSpanID(4),\n\t\t},\n\t}\n}\n\nfunc getTags() model.KeyValues {\n\tsomeVal := \"someVal\"\n\treturn model.KeyValues{\n\t\tmodel.String(\"someKey\", someVal),\n\t}\n}\n\nfunc getLogs() []model.Log {\n\tlogTag := \"this is a msg\"\n\treturn []model.Log{\n\t\t{\n\t\t\tTimestamp: time.Now(),\n\t\t\tFields: model.KeyValues{\n\t\t\t\tmodel.String(\"event\", logTag),\n\t\t\t},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/README.md",
    "content": "# Cassandra Schema Management\n\nThe table below lists Jaeger releases in which new cassandra schema were introduced.\n\n| Jaeger Version                                                         | Cassandra Schema File | Notes                                                                                                                                 |\n|------------------------------------------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------|\n| [0.5.0](https://github.com/jaegertracing/jaeger/releases/tag/v0.5.0)   | `v001.cql.tmpl`       |                                                                                                                                       |\n| [1.10.0](https://github.com/jaegertracing/jaeger/releases/tag/v1.10.0) | `v002.cql.tmpl`       | See [CHANGELOG.md](https://github.com/jaegertracing/jaeger/blob/main/CHANGELOG.md#1100-2019-02-15) for more details on the migration. |\n| [1.16.0](https://github.com/jaegertracing/jaeger/releases/tag/v1.16.0) | `v003.cql.tmpl`       | See [CHANGELOG.md](https://github.com/jaegertracing/jaeger/blob/main/CHANGELOG.md#1160-2019-12-17) for more details on the migration. |\n| [1.26.0](https://github.com/jaegertracing/jaeger/releases/tag/v1.26.0) | `v004.cql.tmpl`       | See [CHANGELOG.md](https://github.com/jaegertracing/jaeger/blob/main/CHANGELOG.md#1260-2021-09-06) for more details on the migration. |\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/create.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nfunction usage {\n    >&2 echo \"Error: $1\"\n    >&2 echo \"\"\n    >&2 echo \"Usage: MODE=(prod|test) [PARAM=value ...] $0 [template-file] | cqlsh\"\n    >&2 echo \"\"\n    >&2 echo \"The following parameters can be set via environment:\"\n    >&2 echo \"  MODE               - prod or test. Test keyspace is usable on a single node cluster (no replication)\"\n    >&2 echo \"  DATACENTER         - datacenter name for network topology used in prod (optional in MODE=test)\"\n    >&2 echo \"  TRACE_TTL          - time to live for trace data, in seconds (default: 172800, 2 days)\"\n    >&2 echo \"  DEPENDENCIES_TTL   - time to live for dependencies data, in seconds (default: 0, no TTL)\"\n    >&2 echo \"  KEYSPACE           - keyspace (default: jaeger_v1_{datacenter})\"\n    >&2 echo \"  REPLICATION        - complete replication configuration (if set, overrides REPLICATION_FACTOR and DATACENTER)\"\n    >&2 echo \"  REPLICATION_FACTOR - replication factor for prod (default: 2 for prod, 1 for test)\"\n    >&2 echo \"  VERSION            - Cassandra backend version, 3 or 4 (default: 4). Ignored if template is provided.\"\n    >&2 echo \"\"\n    >&2 echo \"The template-file argument must be fully qualified path to a v00#.cql.tmpl template file.\"\n    >&2 echo \"If omitted, the template file with the highest available version will be used.\"\n    exit 1\n}\n\ntrace_ttl=${TRACE_TTL:-172800}\ndependencies_ttl=${DEPENDENCIES_TTL:-0}\ncas_version=${VERSION:-4}\ncas_version=$(echo \"$cas_version\" | tr -cd '0-9')\ntemplate=$1\nif [[ \"$template\" == \"\" ]]; then\n    case \"$cas_version\" in\n        3)\n            template=$(dirname $0)/v003.cql.tmpl\n            ;;\n        4)\n            template=$(dirname $0)/v004.cql.tmpl\n            ;;\n        5)\n            template=$(dirname $0)/v004.cql.tmpl\n            ;;\n        *)\n            template=$(ls $(dirname $0)/*cql.tmpl | sort | tail -1)\n            ;;\n    esac\nfi\n\nif [[ \"$MODE\" == \"\" ]]; then\n    usage \"missing MODE parameter\"\nelif [[ \"$MODE\" == \"prod\" ]]; then\n    if [[ -n \"$REPLICATION\" ]]; then\n        replication=\"$REPLICATION\"\n    else\n        if [[ \"$DATACENTER\" == \"\" ]]; then usage \"missing DATACENTER parameter for prod mode\"; fi\n        datacenter=$DATACENTER\n        replication_factor=${REPLICATION_FACTOR:-2}\n        replication=\"{'class': 'NetworkTopologyStrategy', '$datacenter': '${replication_factor}' }\"\n    fi\nelif [[ \"$MODE\" == \"test\" ]]; then\n    if [[ -n \"$REPLICATION\" ]]; then\n        replication=\"$REPLICATION\"\n    else\n        datacenter=${DATACENTER:-'test'}\n        replication_factor=${REPLICATION_FACTOR:-1}\n        replication=\"{'class': 'SimpleStrategy', 'replication_factor': '${replication_factor}'}\"\n    fi\nelse\n    usage \"invalid MODE=$MODE, expecting 'prod' or 'test'\"\nfi\n\nkeyspace=${KEYSPACE:-\"jaeger_v1_${datacenter}\"}\n\nif [[ $keyspace =~ [^a-zA-Z0-9_] ]]; then\n    usage \"invalid characters in KEYSPACE=$keyspace parameter, please use letters, digits or underscores\"\nfi\n\nif [ ! -z \"$COMPACTION_WINDOW\" ]; then\n    if echo \"$COMPACTION_WINDOW\" | grep -E -q '^[0-9]+[mhd]$'; then\n        compaction_window_size=\"$(echo \"$COMPACTION_WINDOW\" | sed 's/[mhd]//')\"\n        compaction_window_unit=\"$(echo \"$COMPACTION_WINDOW\" | sed 's/[0-9]//g')\"\n    else\n        usage \"Invalid compaction window size format. Please use numeric value followed by 'm' for minutes, 'h' for hours, or 'd' for days.\"\n    fi\nelse\n    trace_ttl_minutes=$(( $trace_ttl / 60 ))\n    # Taking the ceiling of the result\n    compaction_window_size=$(( ($trace_ttl_minutes + 30 - 1) / 30 ))\n    compaction_window_unit=\"m\"\nfi\n\ncase \"$compaction_window_unit\" in\n    m) compaction_window_unit=\"MINUTES\" ;;\n    h) compaction_window_unit=\"HOURS\" ;;\n    d) compaction_window_unit=\"DAYS\" ;;\nesac\n\n>&2 cat <<EOF\nUsing template file $template with parameters:\n    mode = $MODE\n    datacenter = $datacenter\n    keyspace = $keyspace\n    replication = ${replication}\n    trace_ttl = ${trace_ttl}\n    dependencies_ttl = ${dependencies_ttl}\n    compaction_window_size = ${compaction_window_size}\n    compaction_window_unit = ${compaction_window_unit}\nEOF\n\n# strip out comments, collapse multiple adjacent empty lines (cat -s), substitute variables\ncat $template | sed \\\n    -e 's/--.*$//g'                                               \\\n    -e 's/^\\s*$//g'                                               \\\n    -e \"s/\\${keyspace}/${keyspace}/g\"                             \\\n    -e \"s/\\${replication}/${replication}/g\"                       \\\n    -e \"s/\\${trace_ttl}/${trace_ttl}/g\"                           \\\n    -e \"s/\\${dependencies_ttl}/${dependencies_ttl}/g\"             \\\n    -e \"s/\\${compaction_window_size}/${compaction_window_size}/g\" \\\n    -e \"s/\\${compaction_window_unit}/${compaction_window_unit}/g\" | cat -s\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/create.test.sh",
    "content": "#!/bin/bash\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# This script uses https://github.com/kward/shunit2 to run unit tests.\n# The path to this repo must be provided via SHUNIT2 env var.\n\nSHUNIT2=\"${SHUNIT2:?'expecting SHUNIT2 env var pointing to a dir with https://github.com/kward/shunit2 clone'}\"\n\n\ncreateScript=\"$(dirname $0)/create.sh\"\n\n\nunset MODE\nunset DATACENTER\nunset KEYSPACE\nunset REPLICATION\nunset REPLICATION_FACTOR\nunset TRACE_TTL\nunset DEPENDENCIES_TTL\nunset COMPACTION_WINDOW\nunset VERSION\n\ntestRequireMode() {\n    err=$(bash \"$createScript\" 2>&1)\n    assertContains \"$err\" \"missing MODE parameter\"\n}\n\ntestInvalidMode() {\n    err=$(MODE=invalid bash \"$createScript\" 2>&1)\n    assertContains \"$err\" \"invalid MODE=invalid, expecting 'prod' or 'test'\"\n}\n\ntestProdModeRequiresDatacenter() {\n    err=$(MODE=prod bash \"$createScript\" 2>&1)\n    assertContains \"$err\" \"missing DATACENTER parameter for prod mode\"\n}\n\ntestProdModeWithDatacenter() {\n    out=$(MODE=prod DATACENTER=dc1 bash \"$createScript\" 2>&1)\n    assertContains \"$out\" \"mode = prod\"\n    assertContains \"$out\" \"datacenter = dc1\"\n    assertContains \"$out\" \"replication = {'class': 'NetworkTopologyStrategy', 'dc1': '2' }\"\n}\n\ntestTestMode() {\n    out=$(MODE=test bash \"$createScript\" 2>&1)\n    assertContains \"$out\" \"mode = test\"\n    assertContains \"$out\" \"datacenter = test\"\n    assertContains \"$out\" \"replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}\"\n}\n\ntestCustomTTL() {\n    out=$(MODE=test TRACE_TTL=86400 DEPENDENCIES_TTL=172800 bash \"$createScript\" 2>&1)\n    assertContains \"$out\" \"trace_ttl = 86400\"\n    assertContains \"$out\" \"dependencies_ttl = 172800\"\n}\n\ntestInvalidKeyspace() {\n    err=$(MODE=test KEYSPACE=invalid-keyspace bash \"$createScript\" 2>&1)\n    assertContains \"$err\" \"invalid characters in KEYSPACE\"\n}\n\ntestValidKeyspace() {\n    out=$(MODE=test KEYSPACE=valid_keyspace_123 bash \"$createScript\" 2>&1)\n    assertContains \"$out\" \"keyspace = valid_keyspace_123\"\n}\n\ntestCustomCompactionWindow() {\n    out=$(MODE=test COMPACTION_WINDOW=24h bash \"$createScript\" 2>&1)\n    assertContains \"$out\" \"compaction_window_size = 24\"\n    assertContains \"$out\" \"compaction_window_unit = HOURS\"\n}\n\ntestInvalidCompactionWindow() {\n    err=$(MODE=test COMPACTION_WINDOW=24x bash \"$createScript\" 2>&1)\n    assertContains \"$err\" \"Invalid compaction window size format\"\n}\n\ntestCustomVersion() {\n    out=$(MODE=test VERSION=3 bash \"$createScript\" 2>&1)\n    assertContains \"$out\" \"v003.cql.tmpl\"\n}\n\n\nsource \"${SHUNIT2}/shunit2\"\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/docker.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n#\n# This script is used in the Docker image jaegertracing/jaeger-cassandra-schema\n# that allows installing Jaeger keyspace and schema without installing cqlsh.\n\nCQLSH=${CQLSH:-\"/opt/cassandra/bin/cqlsh\"}\nCQLSH_HOST=${CQLSH_HOST:-\"cassandra\"}\nCQLSH_PORT=${CQLSH_PORT:-\"9042\"}\nCQLSH_SSL=${CQLSH_SSL:-\"\"}\nCASSANDRA_WAIT_TIMEOUT=${CASSANDRA_WAIT_TIMEOUT:-\"60\"}\nDATACENTER=${DATACENTER:-\"dc1\"}\nKEYSPACE=${KEYSPACE:-\"jaeger_v1_${DATACENTER}\"}\nMODE=${MODE:-\"test\"}\nTEMPLATE=${TEMPLATE:-\"\"}\nUSER=${CASSANDRA_USERNAME:-\"\"}\nPASSWORD=${CASSANDRA_PASSWORD:-\"\"}\nSCHEMA_SCRIPT=${SCHEMA_SCRIPT:-\"/cassandra-schema/create.sh\"}\n\nCQLSH_CMD=\"${CQLSH} ${CQLSH_SSL} ${CQLSH_HOST} ${CQLSH_PORT}\"\nif [ ! -z \"$PASSWORD\" ]; then\n  CQLSH_CMD=\"${CQLSH_CMD} -u ${USER} -p ${PASSWORD}\"\nfi\n\ntotal_wait=0\nwhile true\ndo\n  echo \"Checking if Cassandra is up at ${CQLSH_HOST}:${CQLSH_PORT}.\"\n  ${CQLSH_CMD} -e \"describe keyspaces\"\n  if (( $? == 0 )); then\n    echo \"Cassandra connection established.\"\n    break\n  else\n    if (( total_wait >= ${CASSANDRA_WAIT_TIMEOUT} )); then\n      echo \"Timed out waiting for Cassandra.\"\n      exit 1\n    fi\n    echo \"Cassandra is still not up at ${CQLSH_HOST}:${CQLSH_PORT}. Waiting 1 second.\"\n    sleep 1s\n    ((total_wait++))\n  fi\ndone\n\n# Extract cassandra version\n#\n# $ cqlsh -e \"show version\"\n# [cqlsh 5.0.1 | Cassandra 3.11.11 | CQL spec 3.4.4 | Native protocol v4]\nVERSION=\nif [ -z \"$TEMPLATE\" ]; then\n  VERSION=$(${CQLSH_CMD} -e \"show version\" \\\n      | awk -F \"|\" '{print $2}' \\\n      | awk -F \" \" '{print $2}' \\\n      | awk -F \".\" '{print $1}' \\\n  )\n  echo \"Cassandra version detected: ${VERSION}\"\nfi\n\necho \"Generating the schema for the keyspace ${KEYSPACE} and datacenter ${DATACENTER}.\"\n\nset -e -o pipefail\n\nMODE=\"${MODE}\" DATACENTER=\"${DATACENTER}\" KEYSPACE=\"${KEYSPACE}\" VERSION=\"${VERSION}\" ${SCHEMA_SCRIPT} \"${TEMPLATE}\" | ${CQLSH_CMD}\n\necho \"Schema generated.\"\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/migration/V002toV003.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Create a new operation_names_v2 table and copy all data from operation_names table\n# Sample usage: KEYSPACE=jaeger_v1 CQL_CMD='cqlsh host 9042 -u test_user -p test_password --request-timeout=3000' bash\n# ./v002tov003.sh\n\nset -euo pipefail\n\nfunction usage {\n    >&2 echo \"Error: $1\"\n    >&2 echo \"\"\n    >&2 echo \"Usage: KEYSPACE={keyspace} CQL_CMD={cql_cmd} $0\"\n    >&2 echo \"\"\n    >&2 echo \"The following parameters can be set via environment:\"\n    >&2 echo \"  KEYSPACE           - keyspace\"\n    >&2 echo \"  CQL_CMD            - cqlsh host port -u user -p password\"\n    >&2 echo \"\"\n    exit 1\n}\n\nconfirm() {\n    read -r -p \"${1:-Continue? [y/N]} \" response\n    case \"$response\" in\n        [yY][eE][sS]|[yY])\n            true\n            ;;\n        *)\n            exit 1\n            ;;\n    esac\n}\n\nif [[ ${KEYSPACE} == \"\" ]]; then\n   usage \"missing KEYSPACE parameter\"\nfi\n\nif [[ ${KEYSPACE} =~ [^a-zA-Z0-9_] ]]; then\n    usage \"invalid characters in KEYSPACE=$KEYSPACE parameter, please use letters, digits or underscores\"\nfi\n\nkeyspace=${KEYSPACE}\nold_table=operation_names\nnew_table=operation_names_v2\ncqlsh_cmd=${CQL_CMD}\n\nif [[ ${cqlsh_cmd} == \"\" ]]; then\n   cqlsh_cmd=cqlsh\nfi\n\necho \"Using cql command: $cqlsh_cmd\"\n\nrow_count=$(${cqlsh_cmd} -e \"select count(*) from $keyspace.$old_table;\"|head -4|tail -1| tr -d ' ')\n\necho \"About to copy $row_count rows to new table...\"\n\nconfirm\n\n${cqlsh_cmd} -e \"COPY $keyspace.$old_table (service_name, operation_name) to '$old_table.csv';\"\n\nif [[ ! -f ${old_table}.csv ]]; then\n    echo \"Could not find $old_table.csv. Backup from cassandra was probably not successful\"\n    exit 1\nfi\n\ncsv_rows=$(wc -l ${old_table}.csv | tr -dc '0-9')\n\necho \"Generating data for new table...\"\nwhile IFS=\",\" read service_name operation_name; do\n    echo \"$service_name,,$operation_name\"\ndone < ${old_table}.csv > ${new_table}.csv\n\nttl=$(${cqlsh_cmd} -e \"select default_time_to_live from system_schema.tables WHERE keyspace_name='$keyspace' AND table_name='$old_table';\"|head -4|tail -1|tr -d ' ')\n\necho \"Creating new table $new_table with ttl: $ttl\"\n\n${cqlsh_cmd} -e \"CREATE TABLE IF NOT EXISTS $keyspace.$new_table (\n    service_name        text,\n    span_kind           text,\n    operation_name      text,\n    PRIMARY KEY ((service_name), span_kind, operation_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = $ttl\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800;\"\n\necho \"Import data to new table: $keyspace.$new_table from $new_table.csv\"\n\n# empty string will be inserted as empty string instead of null\n${cqlsh_cmd} -e \"COPY $keyspace.$new_table (service_name, span_kind, operation_name)\n    FROM '$new_table.csv'\n    WITH NULL='NIL';\"\n\necho \"Data from old table are successfully imported to new table!\"\n\necho \"Before finish, do you want to delete old table: $keyspace.$old_table?\"\nconfirm\n${cqlsh_cmd} -e \"DROP TABLE IF EXISTS $keyspace.$old_table;\""
  },
  {
    "path": "internal/storage/v1/cassandra/schema/migration/v001tov002part1.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euo pipefail\n\nfunction usage {\n    >&2 echo \"Error: $1\"\n    >&2 echo \"\"\n    >&2 echo \"Usage: KEYSPACE={keyspace} $0\"\n    >&2 echo \"\"\n    >&2 echo \"The following parameters can be set via environment:\"\n    >&2 echo \"  KEYSPACE           - keyspace\"\n    >&2 echo \"  TIMEOUT            - cqlsh request timeout\"\n    >&2 echo \"\"\n    exit 1\n}\n\nconfirm() {\n    read -r -p \"${1:-Are you sure? [y/N]} \" response\n    case \"$response\" in\n        [yY][eE][sS]|[yY])\n            true\n            ;;\n        *)\n            exit 1\n            ;;\n    esac\n}\n\nkeyspace=${KEYSPACE}\ntimeout=${TIMEOUT:-\"60\"}\ncqlsh_cmd=\"cqlsh --request-timeout=$timeout\"\n\nif [[ ${keyspace} == \"\" ]]; then\n   usage \"missing KEYSPACE parameter\"\nfi\n\nif [[ ${keyspace} =~ [^a-zA-Z0-9_] ]]; then\n    usage \"invalid characters in KEYSPACE=$keyspace parameter, please use letters, digits or underscores\"\nfi\n\nrow_count=$($cqlsh_cmd -e \"select count(*) from $keyspace.dependencies;\"|head -4|tail -1| tr -d ' ')\n\necho \"About to copy $row_count rows.\"\nconfirm\n\n$cqlsh_cmd -e \"COPY $keyspace.dependencies (ts, dependencies) to 'dependencies.csv';\"\n\nif [ ! -f dependencies.csv ]; then\n    echo \"Could not find dependencies.csv. Backup from cassandra was probably not successful\"\n    exit 1\nfi\n\nif [ ${row_count} -ne $(wc -l dependencies.csv | cut -f 1 -d ' ') ]; then\n    echo \"Number of rows in file is not equal to number of rows in cassandra\"\n    exit 1\nfi\n\nwhile IFS=\",\" read ts dependency; do\n    bucket=`date +\"%Y-%m-%d%z\" -d \"$ts\"`\n    echo \"$bucket,$ts,$dependency\"\ndone < dependencies.csv > dependencies_datebucket.csv\n\ndependencies_ttl=$($cqlsh_cmd -e \"select default_time_to_live from system_schema.tables WHERE keyspace_name='$keyspace' AND table_name='dependencies';\"|head -4|tail -1|tr -d ' ')\n\necho \"Setting dependencies_ttl to $dependencies_ttl\"\n\n$cqlsh_cmd -e \"ALTER TYPE $keyspace.dependency ADD source text;\"\n\n$cqlsh_cmd -e \"CREATE TABLE $keyspace.dependencies_v2 (\n    ts_bucket    timestamp,\n    ts           timestamp,\n    dependencies list<frozen<dependency>>,\n    PRIMARY KEY (ts_bucket, ts)\n) WITH CLUSTERING ORDER BY (ts DESC)\n    AND compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = $dependencies_ttl;\n\"\n\n$cqlsh_cmd -e \"COPY $keyspace.dependencies_v2 (ts_bucket, ts, dependencies) FROM 'dependencies_datebucket.csv';\"\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/migration/v001tov002part2.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euo pipefail\n\nfunction usage {\n    >&2 echo \"Error: $1\"\n    >&2 echo \"\"\n    >&2 echo \"Usage: KEYSPACE={keyspace} $0\"\n    >&2 echo \"\"\n    >&2 echo \"The following parameters can be set via environment:\"\n    >&2 echo \"  KEYSPACE           - keyspace\"\n    >&2 echo \"  TIMEOUT            - cqlsh request timeout\"\n    >&2 echo \"\"\n    exit 1\n}\n\nconfirm() {\n    read -r -p \"${1:-Are you sure? [y/N]} \" response\n    case \"$response\" in\n        [yY][eE][sS]|[yY])\n            true\n            ;;\n        *)\n            exit 1\n            ;;\n    esac\n}\n\nkeyspace=${KEYSPACE}\ntimeout=${TIMEOUT}\ncqlsh_cmd=cqlsh --request-timeout=$timeout\n\nif [[ ${keyspace} == \"\" ]]; then\n   usage \"missing KEYSPACE parameter\"\nfi\n\nif [[ ${keyspace} =~ [^a-zA-Z0-9_] ]]; then\n    usage \"invalid characters in KEYSPACE=$keyspace parameter, please use letters, digits or underscores\"\nfi\n\n\nrow_count=$($cqlsh_cmd -e \"select count(*) from $keyspace.dependencies;\"|head -4|tail -1| tr -d ' ')\n\necho \"About to delete $row_count rows.\"\nconfirm\n\n$cqlsh_cmd -e \"DROP INDEX IF EXISTS $keyspace.ts_index;\"\n$cqlsh_cmd -e \"DROP TABLE IF EXISTS $keyspace.dependencies;\"\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage schema\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/schema.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage schema\n\nimport (\n\t\"bytes\"\n\t\"embed\"\n\t\"errors\"\n\t\"fmt\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n)\n\n//go:embed v004-go-tmpl.cql.tmpl\nvar schemaFile embed.FS\n\ntype templateParams struct {\n\t// Keyspace in which tables and types will be created for storage\n\tKeyspace string\n\t// Replication is the replication strategy used. Ex: \"{'class': 'NetworkTopologyStrategy', 'replication_factor': '1' }\"\n\tReplication string\n\t// CompactionWindowInMinutes is constructed from CompactionWindow for using in template\n\tCompactionWindowInMinutes int64\n\t// TraceTTLInSeconds is constructed from TraceTTL for using in template\n\tTraceTTLInSeconds int64\n\t// DependenciesTTLInSeconds is constructed from DependenciesTTL for using in template\n\tDependenciesTTLInSeconds int64\n}\n\ntype Creator struct {\n\tsession cassandra.Session\n\tschema  config.Schema\n}\n\n// NewSchemaCreator returns a new SchemaCreator\nfunc NewSchemaCreator(session cassandra.Session, schema config.Schema) *Creator {\n\treturn &Creator{\n\t\tsession: session,\n\t\tschema:  schema,\n\t}\n}\n\nfunc (sc *Creator) constructTemplateParams() templateParams {\n\treplicationConfig := fmt.Sprintf(\"{'class': 'SimpleStrategy', 'replication_factor': '%d'}\", sc.schema.ReplicationFactor)\n\tif sc.schema.Datacenter != \"\" {\n\t\treplicationConfig = fmt.Sprintf(\"{'class': 'NetworkTopologyStrategy', '%s': '%d' }\", sc.schema.Datacenter, sc.schema.ReplicationFactor)\n\t}\n\n\treturn templateParams{\n\t\tKeyspace:                  sc.schema.Keyspace,\n\t\tReplication:               replicationConfig,\n\t\tCompactionWindowInMinutes: int64(sc.schema.CompactionWindow / time.Minute),\n\t\tTraceTTLInSeconds:         int64(sc.schema.TraceTTL / time.Second),\n\t\tDependenciesTTLInSeconds:  int64(sc.schema.DependenciesTTL / time.Second),\n\t}\n}\n\nfunc (*Creator) getQueryFileAsBytes(fileName string, params templateParams) ([]byte, error) {\n\ttmpl, err := template.ParseFS(schemaFile, fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar result bytes.Buffer\n\terr = tmpl.Execute(&result, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result.Bytes(), nil\n}\n\nfunc (*Creator) getQueriesFromBytes(queryFile []byte) ([]string, error) {\n\tlines := bytes.Split(queryFile, []byte(\"\\n\"))\n\n\tvar extractedLines [][]byte\n\n\tfor _, line := range lines {\n\t\t// Remove any comments, if at the end of the line\n\t\tcommentIndex := bytes.Index(line, []byte(`--`))\n\t\tif commentIndex != -1 {\n\t\t\t// remove everything after comment\n\t\t\tline = line[0:commentIndex]\n\t\t}\n\n\t\ttrimmedLine := bytes.TrimSpace(line)\n\n\t\tif len(trimmedLine) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\textractedLines = append(extractedLines, trimmedLine)\n\t}\n\n\tvar queries []string\n\n\t// Construct individual queries strings\n\tvar queryString string\n\tfor _, line := range extractedLines {\n\t\tqueryString += string(line) + \"\\n\"\n\t\tif bytes.HasSuffix(line, []byte(\";\")) {\n\t\t\tqueries = append(queries, queryString)\n\t\t\tqueryString = \"\"\n\t\t}\n\t}\n\n\tif queryString != \"\" {\n\t\treturn nil, errors.New(`query exists in template without \";\"`)\n\t}\n\n\treturn queries, nil\n}\n\nfunc (sc *Creator) getCassandraQueriesFromQueryStrings(queries []string) []cassandra.Query {\n\tvar casQueries []cassandra.Query\n\n\tfor _, query := range queries {\n\t\tcasQueries = append(casQueries, sc.session.Query(query))\n\t}\n\n\treturn casQueries\n}\n\nfunc (sc *Creator) contructSchemaQueries() ([]cassandra.Query, error) {\n\tparams := sc.constructTemplateParams()\n\n\tqueryFile, err := sc.getQueryFileAsBytes(`v004-go-tmpl.cql.tmpl`, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tqueryStrings, err := sc.getQueriesFromBytes(queryFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcasQueries := sc.getCassandraQueriesFromQueryStrings(queryStrings)\n\n\treturn casQueries, nil\n}\n\nfunc (sc *Creator) CreateSchemaIfNotPresent() error {\n\tcasQueries, err := sc.contructSchemaQueries()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, query := range casQueries {\n\t\tif err := query.Exec(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/schema_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage schema\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestQueryGenerationFromBytes(t *testing.T) {\n\tqueriesAsString := `\nquery1 -- comment (this should be removed)\nquery1-continue\nquery1-finished; --\n\n\nquery2;\nquery-3 query-3-continue query-3-finished;\n`\n\texpGeneratedQueries := []string{\n\t\t`query1\nquery1-continue\nquery1-finished;\n`,\n\t\t`query2;\n`,\n\t\t`query-3 query-3-continue query-3-finished;\n`,\n\t}\n\n\tsc := Creator{}\n\tqueriesAsBytes := []byte(queriesAsString)\n\tqueries, err := sc.getQueriesFromBytes(queriesAsBytes)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, queries, len(expGeneratedQueries))\n\n\tfor i := range len(expGeneratedQueries) {\n\t\trequire.Equal(t, expGeneratedQueries[i], queries[i])\n\t}\n}\n\nfunc TestInvalidQueryTemplate(t *testing.T) {\n\tqueriesAsString := `\n\tquery1 -- comment (this should be removed)\n\tquery1-continue\n\tquery1-finished; --\n\n\n\tquery2;\n\tquery-3 query-3-continue query-3-finished -- missing semicolon\n\t`\n\tsc := Creator{}\n\tqueriesAsBytes := []byte(queriesAsString)\n\t_, err := sc.getQueriesFromBytes(queriesAsBytes)\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/v001.cql.tmpl",
    "content": "--\n-- Creates Cassandra keyspace with tables for traces and dependencies.\n--\n-- Required parameters:\n--\n--   keyspace\n--     name of the keyspace\n--   replication\n--     replication strategy for the keyspace, such as\n--       for prod environments\n--         {'class': 'NetworkTopologyStrategy', '$datacenter': '${replication_factor}' }\n--       for test environments\n--         {'class': 'SimpleStrategy', 'replication_factor': '1'}\n--   trace_ttl\n--     default time to live for trace data, in seconds\n--   dependencies_ttl\n--     default time to live for dependencies data, in seconds (0 for no TTL)\n--\n-- Non-configurable settings:\n--   gc_grace_seconds is non-zero, see: http://www.uberobert.com/cassandra_gc_grace_disables_hinted_handoff/\n--   For TTL of 2 days, compaction window is 1 hour, rule of thumb here: http://thelastpickle.com/blog/2016/12/08/TWCS-part1.html\n\nCREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH replication = ${replication};\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.keyvalue (\n    key             text,\n    value_type      text,\n    value_string    text,\n    value_bool      boolean,\n    value_long      bigint,\n    value_double    double,\n    value_binary    blob,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.log (\n    ts      bigint, // microseconds since epoch\n    fields  list<frozen<keyvalue>>,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.span_ref (\n    ref_type        text,\n    trace_id        blob,\n    span_id         bigint,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.process (\n    service_name    text,\n    tags            list<frozen<keyvalue>>,\n);\n\n-- Notice we have span_hash. This exists only for zipkin backwards compat. Zipkin allows spans with the same ID.\n-- Note: Cassandra re-orders non-PK columns alphabetically, so the table looks differently in CQLSH \"describe table\".\n-- start_time is bigint instead of timestamp as we require microsecond precision\nCREATE TABLE IF NOT EXISTS ${keyspace}.traces (\n    trace_id        blob,\n    span_id         bigint,\n    span_hash       bigint,\n    parent_id       bigint,\n    operation_name  text,\n    flags           int,\n    start_time      bigint, // microseconds since epoch\n    duration        bigint, // microseconds\n    tags            list<frozen<keyvalue>>,\n    logs            list<frozen<log>>,\n    refs            list<frozen<span_ref>>,\n    process         frozen<process>,\n    PRIMARY KEY (trace_id, span_id, span_hash)\n)\n    WITH compaction = {\n        'compaction_window_size': '1', \n        'compaction_window_unit': 'HOURS', \n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_names (\n    service_name text,\n    PRIMARY KEY (service_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy' \n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.operation_names (\n    service_name        text,\n    operation_name      text,\n    PRIMARY KEY ((service_name), operation_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- index of trace IDs by service + operation names, sorted by span start_time.\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_operation_index (\n    service_name        text,\n    operation_name      text,\n    start_time          bigint, // microseconds since epoch\n    trace_id            blob,\n    PRIMARY KEY ((service_name, operation_name), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1', \n        'compaction_window_unit': 'HOURS', \n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_name_index (\n    service_name      text,\n    bucket            int,\n    start_time        bigint, // microseconds since epoch\n    trace_id          blob,\n    PRIMARY KEY ((service_name, bucket), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1', \n        'compaction_window_unit': 'HOURS', \n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.duration_index (\n    service_name    text,      // service name\n    operation_name  text,      // operation name, or blank for queries without span name\n    bucket          timestamp, // time bucket, - the start_time of the given span rounded to an hour\n    duration        bigint,    // span duration, in microseconds\n    start_time      bigint,    // microseconds since epoch\n    trace_id        blob,\n    PRIMARY KEY ((service_name, operation_name, bucket), duration, start_time, trace_id)\n) WITH CLUSTERING ORDER BY (duration DESC, start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1', \n        'compaction_window_unit': 'HOURS', \n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- a bucketing strategy may have to be added for tag queries\n-- we can make this table even better by adding a timestamp to it\nCREATE TABLE IF NOT EXISTS ${keyspace}.tag_index (\n    service_name    text,\n    tag_key         text,\n    tag_value       text,\n    start_time      bigint, // microseconds since epoch\n    trace_id        blob,\n    span_id         bigint,\n    PRIMARY KEY ((service_name, tag_key, tag_value), start_time, trace_id, span_id)\n)\n    WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1', \n        'compaction_window_unit': 'HOURS', \n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.dependency (\n    parent          text,\n    child           text,\n    call_count      bigint,\n);\n\n-- compaction strategy is intentionally different as compared to other tables due to the size of dependencies data\n-- note we have to write ts twice (once as ts_index). This is because we cannot make a SASI index on the primary key\nCREATE TABLE IF NOT EXISTS ${keyspace}.dependencies (\n    ts          timestamp,\n    ts_index    timestamp,\n    dependencies list<frozen<dependency>>,\n    PRIMARY KEY (ts)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = ${dependencies_ttl};\n\nCREATE CUSTOM INDEX IF NOT EXISTS ON ${keyspace}.dependencies (ts_index) \n    USING 'org.apache.cassandra.index.sasi.SASIIndex' \n    WITH OPTIONS = {'mode': 'SPARSE'};\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/v002.cql.tmpl",
    "content": "--\n-- Creates Cassandra keyspace with tables for traces and dependencies.\n--\n-- Required parameters:\n--\n--   keyspace\n--     name of the keyspace\n--   replication\n--     replication strategy for the keyspace, such as\n--       for prod environments\n--         {'class': 'NetworkTopologyStrategy', '$datacenter': '${replication_factor}' }\n--       for test environments\n--         {'class': 'SimpleStrategy', 'replication_factor': '1'}\n--   trace_ttl\n--     default time to live for trace data, in seconds\n--   dependencies_ttl\n--     default time to live for dependencies data, in seconds (0 for no TTL)\n--\n-- Non-configurable settings:\n--   gc_grace_seconds is non-zero, see: http://www.uberobert.com/cassandra_gc_grace_disables_hinted_handoff/\n--   For TTL of 2 days, compaction window is 1 hour, rule of thumb here: http://thelastpickle.com/blog/2016/12/08/TWCS-part1.html\n\nCREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH replication = ${replication};\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.keyvalue (\n    key             text,\n    value_type      text,\n    value_string    text,\n    value_bool      boolean,\n    value_long      bigint,\n    value_double    double,\n    value_binary    blob,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.log (\n    ts      bigint, // microseconds since epoch\n    fields  list<frozen<keyvalue>>,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.span_ref (\n    ref_type        text,\n    trace_id        blob,\n    span_id         bigint,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.process (\n    service_name    text,\n    tags            list<frozen<keyvalue>>,\n);\n\n-- Notice we have span_hash. This exists only for zipkin backwards compat. Zipkin allows spans with the same ID.\n-- Note: Cassandra re-orders non-PK columns alphabetically, so the table looks differently in CQLSH \"describe table\".\n-- start_time is bigint instead of timestamp as we require microsecond precision\nCREATE TABLE IF NOT EXISTS ${keyspace}.traces (\n    trace_id        blob,\n    span_id         bigint,\n    span_hash       bigint,\n    parent_id       bigint,\n    operation_name  text,\n    flags           int,\n    start_time      bigint, // microseconds since epoch\n    duration        bigint, // microseconds\n    tags            list<frozen<keyvalue>>,\n    logs            list<frozen<log>>,\n    refs            list<frozen<span_ref>>,\n    process         frozen<process>,\n    PRIMARY KEY (trace_id, span_id, span_hash)\n)\n    WITH compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_names (\n    service_name text,\n    PRIMARY KEY (service_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.operation_names (\n    service_name        text,\n    operation_name      text,\n    PRIMARY KEY ((service_name), operation_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- index of trace IDs by service + operation names, sorted by span start_time.\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_operation_index (\n    service_name        text,\n    operation_name      text,\n    start_time          bigint, // microseconds since epoch\n    trace_id            blob,\n    PRIMARY KEY ((service_name, operation_name), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_name_index (\n    service_name      text,\n    bucket            int,\n    start_time        bigint, // microseconds since epoch\n    trace_id          blob,\n    PRIMARY KEY ((service_name, bucket), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.duration_index (\n    service_name    text,      // service name\n    operation_name  text,      // operation name, or blank for queries without span name\n    bucket          timestamp, // time bucket, - the start_time of the given span rounded to an hour\n    duration        bigint,    // span duration, in microseconds\n    start_time      bigint,    // microseconds since epoch\n    trace_id        blob,\n    PRIMARY KEY ((service_name, operation_name, bucket), duration, start_time, trace_id)\n) WITH CLUSTERING ORDER BY (duration DESC, start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- a bucketing strategy may have to be added for tag queries\n-- we can make this table even better by adding a timestamp to it\nCREATE TABLE IF NOT EXISTS ${keyspace}.tag_index (\n    service_name    text,\n    tag_key         text,\n    tag_value       text,\n    start_time      bigint, // microseconds since epoch\n    trace_id        blob,\n    span_id         bigint,\n    PRIMARY KEY ((service_name, tag_key, tag_value), start_time, trace_id, span_id)\n)\n    WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.dependency (\n    parent          text,\n    child           text,\n    call_count      bigint,\n    source          text,\n);\n\n-- compaction strategy is intentionally different as compared to other tables due to the size of dependencies data\nCREATE TABLE IF NOT EXISTS ${keyspace}.dependencies_v2 (\n    ts_bucket    timestamp,\n    ts           timestamp,\n    dependencies list<frozen<dependency>>,\n    PRIMARY KEY (ts_bucket, ts)\n) WITH CLUSTERING ORDER BY (ts DESC)\n    AND compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = ${dependencies_ttl};\n"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/v003.cql.tmpl",
    "content": "--\n-- Creates Cassandra keyspace with tables for traces and dependencies.\n--\n-- Required parameters:\n--\n--   keyspace\n--     name of the keyspace\n--   replication\n--     replication strategy for the keyspace, such as\n--       for prod environments\n--         {'class': 'NetworkTopologyStrategy', '$datacenter': '${replication_factor}' }\n--       for test environments\n--         {'class': 'SimpleStrategy', 'replication_factor': '1'}\n--   trace_ttl\n--     default time to live for trace data, in seconds\n--   dependencies_ttl\n--     default time to live for dependencies data, in seconds (0 for no TTL)\n--\n-- Non-configurable settings:\n--   gc_grace_seconds is non-zero, see: http://www.uberobert.com/cassandra_gc_grace_disables_hinted_handoff/\n--   For TTL of 2 days, compaction window is 1 hour, rule of thumb here: http://thelastpickle.com/blog/2016/12/08/TWCS-part1.html\n\nCREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH replication = ${replication};\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.keyvalue (\n    key             text,\n    value_type      text,\n    value_string    text,\n    value_bool      boolean,\n    value_long      bigint,\n    value_double    double,\n    value_binary    blob,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.log (\n    ts      bigint, // microseconds since epoch\n    fields  list<frozen<keyvalue>>,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.span_ref (\n    ref_type        text,\n    trace_id        blob,\n    span_id         bigint,\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.process (\n    service_name    text,\n    tags            list<frozen<keyvalue>>,\n);\n\n-- Notice we have span_hash. This exists only for zipkin backwards compat. Zipkin allows spans with the same ID.\n-- Note: Cassandra re-orders non-PK columns alphabetically, so the table looks differently in CQLSH \"describe table\".\n-- start_time is bigint instead of timestamp as we require microsecond precision\nCREATE TABLE IF NOT EXISTS ${keyspace}.traces (\n    trace_id        blob,\n    span_id         bigint,\n    span_hash       bigint,\n    parent_id       bigint,\n    operation_name  text,\n    flags           int,\n    start_time      bigint, // microseconds since epoch\n    duration        bigint, // microseconds\n    tags            list<frozen<keyvalue>>,\n    logs            list<frozen<log>>,\n    refs            list<frozen<span_ref>>,\n    process         frozen<process>,\n    PRIMARY KEY (trace_id, span_id, span_hash)\n)\n    WITH compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_names (\n    service_name text,\n    PRIMARY KEY (service_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.operation_names_v2 (\n    service_name        text,\n    span_kind           text,\n    operation_name      text,\n    PRIMARY KEY ((service_name), span_kind, operation_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- index of trace IDs by service + operation names, sorted by span start_time.\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_operation_index (\n    service_name        text,\n    operation_name      text,\n    start_time          bigint, // microseconds since epoch\n    trace_id            blob,\n    PRIMARY KEY ((service_name, operation_name), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_name_index (\n    service_name      text,\n    bucket            int,\n    start_time        bigint, // microseconds since epoch\n    trace_id          blob,\n    PRIMARY KEY ((service_name, bucket), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.duration_index (\n    service_name    text,      // service name\n    operation_name  text,      // operation name, or blank for queries without span name\n    bucket          timestamp, // time bucket, - the start_time of the given span rounded to an hour\n    duration        bigint,    // span duration, in microseconds\n    start_time      bigint,    // microseconds since epoch\n    trace_id        blob,\n    PRIMARY KEY ((service_name, operation_name, bucket), duration, start_time, trace_id)\n) WITH CLUSTERING ORDER BY (duration DESC, start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- a bucketing strategy may have to be added for tag queries\n-- we can make this table even better by adding a timestamp to it\nCREATE TABLE IF NOT EXISTS ${keyspace}.tag_index (\n    service_name    text,\n    tag_key         text,\n    tag_value       text,\n    start_time      bigint, // microseconds since epoch\n    trace_id        blob,\n    span_id         bigint,\n    PRIMARY KEY ((service_name, tag_key, tag_value), start_time, trace_id, span_id)\n)\n    WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND dclocal_read_repair_chance = 0.0\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.dependency (\n    parent          text,\n    child           text,\n    call_count      bigint,\n    source          text,\n);\n\n-- compaction strategy is intentionally different as compared to other tables due to the size of dependencies data\nCREATE TABLE IF NOT EXISTS ${keyspace}.dependencies_v2 (\n    ts_bucket    timestamp,\n    ts           timestamp,\n    dependencies list<frozen<dependency>>,\n    PRIMARY KEY (ts_bucket, ts)\n) WITH CLUSTERING ORDER BY (ts DESC)\n    AND compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = ${dependencies_ttl};\n\n-- adaptive sampling tables\n-- ./internal/storage/v1/cassandra/samplingstore/storage.go\nCREATE TABLE IF NOT EXISTS ${keyspace}.operation_throughput (\n    bucket        int,\n    ts            timeuuid,\n    throughput    text,\n    PRIMARY KEY(bucket, ts)\n) WITH CLUSTERING ORDER BY (ts desc);\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.sampling_probabilities (\n    bucket        int,\n    ts            timeuuid,\n    hostname      text,\n    probabilities text,\n    PRIMARY KEY(bucket, ts)\n) WITH CLUSTERING ORDER BY (ts desc);\n\n-- distributed lock\n-- ./plugin/pkg/distributedlock/cassandra/lock.go\nCREATE TABLE IF NOT EXISTS ${keyspace}.leases (\n    name text,\n    owner text,\n    PRIMARY KEY (name)\n);"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/v004-go-tmpl.cql.tmpl",
    "content": "CREATE KEYSPACE IF NOT EXISTS {{.Keyspace}} WITH replication = {{.Replication}};\n\nCREATE TYPE IF NOT EXISTS {{.Keyspace}}.keyvalue (\n    key             text,\n    value_type      text,\n    value_string    text,\n    value_bool      boolean,\n    value_long      bigint,\n    value_double    double,\n    value_binary    blob\n);\n\nCREATE TYPE IF NOT EXISTS {{.Keyspace}}.log (\n    ts      bigint, -- microseconds since epoch\n    fields  frozen<list<frozen<{{.Keyspace}}.keyvalue>>>\n);\n\nCREATE TYPE IF NOT EXISTS {{.Keyspace}}.span_ref (\n    ref_type        text,\n    trace_id        blob,\n    span_id         bigint\n);\n\nCREATE TYPE IF NOT EXISTS {{.Keyspace}}.process (\n    service_name    text,\n    tags            frozen<list<frozen<{{.Keyspace}}.keyvalue>>>\n);\n\n-- Notice we have span_hash. This exists only for zipkin backwards compat. Zipkin allows spans with the same ID.\n-- Note: Cassandra re-orders non-PK columns alphabetically, so the table looks differently in CQLSH \"describe table\".\n-- start_time is bigint instead of timestamp as we require microsecond precision\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.traces (\n    trace_id        blob,\n    span_id         bigint,\n    span_hash       bigint,\n    parent_id       bigint,\n    operation_name  text,\n    flags           int,\n    start_time      bigint, -- microseconds since epoch\n    duration        bigint, -- microseconds\n    tags            list<frozen<keyvalue>>,\n    logs            list<frozen<log>>,\n    refs            list<frozen<span_ref>>,\n    process         frozen<process>,\n    PRIMARY KEY (trace_id, span_id, span_hash)\n)\n    WITH compaction = {\n        'compaction_window_size': '{{.CompactionWindowInMinutes}}',\n        'compaction_window_unit': 'MINUTES',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.service_names (\n    service_name text,\n    PRIMARY KEY (service_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.operation_names_v2 (\n    service_name        text,\n    span_kind           text,\n    operation_name      text,\n    PRIMARY KEY ((service_name), span_kind, operation_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- index of trace IDs by service + operation names, sorted by span start_time.\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.service_operation_index (\n    service_name        text,\n    operation_name      text,\n    start_time          bigint, -- microseconds since epoch\n    trace_id            blob,\n    PRIMARY KEY ((service_name, operation_name), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.service_name_index (\n    service_name      text,\n    bucket            int,\n    start_time        bigint, -- microseconds since epoch\n    trace_id          blob,\n    PRIMARY KEY ((service_name, bucket), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.duration_index (\n    service_name    text,      -- service name\n    operation_name  text,      -- operation name, or blank for queries without span name\n    bucket          timestamp, -- time bucket, - the start_time of the given span rounded to an hour\n    duration        bigint,    -- span duration, in microseconds\n    start_time      bigint,    -- microseconds since epoch\n    trace_id        blob,\n    PRIMARY KEY ((service_name, operation_name, bucket), duration, start_time, trace_id)\n) WITH CLUSTERING ORDER BY (duration DESC, start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- a bucketing strategy may have to be added for tag queries\n-- we can make this table even better by adding a timestamp to it\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.tag_index (\n    service_name    text,\n    tag_key         text,\n    tag_value       text,\n    start_time      bigint, -- microseconds since epoch\n    trace_id        blob,\n    span_id         bigint,\n    PRIMARY KEY ((service_name, tag_key, tag_value), start_time, trace_id, span_id)\n)\n    WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = {{.TraceTTLInSeconds}}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TYPE IF NOT EXISTS {{.Keyspace}}.dependency (\n    parent          text,\n    child           text,\n    call_count      bigint,\n    source          text\n);\n\n-- compaction strategy is intentionally different as compared to other tables due to the size of dependencies data\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.dependencies_v2 (\n    ts_bucket    timestamp,\n    ts           timestamp,\n    dependencies list<frozen<dependency>>,\n    PRIMARY KEY (ts_bucket, ts)\n) WITH CLUSTERING ORDER BY (ts DESC)\n    AND compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = {{.DependenciesTTLInSeconds}};\n\n-- adaptive sampling tables\n-- ./internal/storage/v1/cassandra/samplingstore/storage.go\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.operation_throughput (\n    bucket        int,\n    ts            timeuuid,\n    throughput    text,\n    PRIMARY KEY(bucket, ts)\n) WITH CLUSTERING ORDER BY (ts desc);\n\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.sampling_probabilities (\n    bucket        int,\n    ts            timeuuid,\n    hostname      text,\n    probabilities text,\n    PRIMARY KEY(bucket, ts)\n) WITH CLUSTERING ORDER BY (ts desc);\n\n-- distributed lock\n-- ./plugin/pkg/distributedlock/cassandra/lock.go\nCREATE TABLE IF NOT EXISTS {{.Keyspace}}.leases (\n    name text,\n    owner text,\n    PRIMARY KEY (name)\n);"
  },
  {
    "path": "internal/storage/v1/cassandra/schema/v004.cql.tmpl",
    "content": "--\n-- Creates Cassandra keyspace with tables for traces and dependencies.\n--\n-- Required parameters:\n--\n--   keyspace\n--     name of the keyspace\n--   replication\n--     replication strategy for the keyspace, such as\n--       for prod environments\n--         {'class': 'NetworkTopologyStrategy', '$datacenter': '${replication_factor}' }\n--       for test environments\n--         {'class': 'SimpleStrategy', 'replication_factor': '1'}\n--   trace_ttl\n--     default time to live for trace data, in seconds\n--   dependencies_ttl\n--     default time to live for dependencies data, in seconds (0 for no TTL)\n--\n-- Non-configurable settings:\n--   gc_grace_seconds is non-zero, see: http://www.uberobert.com/cassandra_gc_grace_disables_hinted_handoff/\n--   For TTL of 2 days, compaction window is 1 hour, rule of thumb here: http://thelastpickle.com/blog/2016/12/08/TWCS-part1.html\n\nCREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH replication = ${replication};\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.keyvalue (\n    key             text,\n    value_type      text,\n    value_string    text,\n    value_bool      boolean,\n    value_long      bigint,\n    value_double    double,\n    value_binary    blob\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.log (\n    ts      bigint, -- microseconds since epoch\n    fields  frozen<list<frozen<${keyspace}.keyvalue>>>\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.span_ref (\n    ref_type        text,\n    trace_id        blob,\n    span_id         bigint\n);\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.process (\n    service_name    text,\n    tags            frozen<list<frozen<${keyspace}.keyvalue>>>\n);\n\n-- Notice we have span_hash. This exists only for zipkin backwards compat. Zipkin allows spans with the same ID.\n-- Note: Cassandra re-orders non-PK columns alphabetically, so the table looks differently in CQLSH \"describe table\".\n-- start_time is bigint instead of timestamp as we require microsecond precision\nCREATE TABLE IF NOT EXISTS ${keyspace}.traces (\n    trace_id        blob,\n    span_id         bigint,\n    span_hash       bigint,\n    parent_id       bigint,\n    operation_name  text,\n    flags           int,\n    start_time      bigint, -- microseconds since epoch\n    duration        bigint, -- microseconds\n    tags            list<frozen<keyvalue>>,\n    logs            list<frozen<log>>,\n    refs            list<frozen<span_ref>>,\n    process         frozen<process>,\n    PRIMARY KEY (trace_id, span_id, span_hash)\n)\n    WITH compaction = {\n        'compaction_window_size': '${compaction_window_size}',\n        'compaction_window_unit': '${compaction_window_unit}',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_names (\n    service_name text,\n    PRIMARY KEY (service_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.operation_names_v2 (\n    service_name        text,\n    span_kind           text,\n    operation_name      text,\n    PRIMARY KEY ((service_name), span_kind, operation_name)\n)\n    WITH compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- index of trace IDs by service + operation names, sorted by span start_time.\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_operation_index (\n    service_name        text,\n    operation_name      text,\n    start_time          bigint, -- microseconds since epoch\n    trace_id            blob,\n    PRIMARY KEY ((service_name, operation_name), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.service_name_index (\n    service_name      text,\n    bucket            int,\n    start_time        bigint, -- microseconds since epoch\n    trace_id          blob,\n    PRIMARY KEY ((service_name, bucket), start_time)\n) WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.duration_index (\n    service_name    text,      -- service name\n    operation_name  text,      -- operation name, or blank for queries without span name\n    bucket          timestamp, -- time bucket, - the start_time of the given span rounded to an hour\n    duration        bigint,    -- span duration, in microseconds\n    start_time      bigint,    -- microseconds since epoch\n    trace_id        blob,\n    PRIMARY KEY ((service_name, operation_name, bucket), duration, start_time, trace_id)\n) WITH CLUSTERING ORDER BY (duration DESC, start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\n-- a bucketing strategy may have to be added for tag queries\n-- we can make this table even better by adding a timestamp to it\nCREATE TABLE IF NOT EXISTS ${keyspace}.tag_index (\n    service_name    text,\n    tag_key         text,\n    tag_value       text,\n    start_time      bigint, -- microseconds since epoch\n    trace_id        blob,\n    span_id         bigint,\n    PRIMARY KEY ((service_name, tag_key, tag_value), start_time, trace_id, span_id)\n)\n    WITH CLUSTERING ORDER BY (start_time DESC)\n    AND compaction = {\n        'compaction_window_size': '1',\n        'compaction_window_unit': 'HOURS',\n        'class': 'org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy'\n    }\n    AND default_time_to_live = ${trace_ttl}\n    AND speculative_retry = 'NONE'\n    AND gc_grace_seconds = 10800; -- 3 hours of downtime acceptable on nodes\n\nCREATE TYPE IF NOT EXISTS ${keyspace}.dependency (\n    parent          text,\n    child           text,\n    call_count      bigint,\n    source          text\n);\n\n-- compaction strategy is intentionally different as compared to other tables due to the size of dependencies data\nCREATE TABLE IF NOT EXISTS ${keyspace}.dependencies_v2 (\n    ts_bucket    timestamp,\n    ts           timestamp,\n    dependencies list<frozen<dependency>>,\n    PRIMARY KEY (ts_bucket, ts)\n) WITH CLUSTERING ORDER BY (ts DESC)\n    AND compaction = {\n        'min_threshold': '4',\n        'max_threshold': '32',\n        'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'\n    }\n    AND default_time_to_live = ${dependencies_ttl};\n\n-- adaptive sampling tables\n-- ./internal/storage/v1/cassandra/samplingstore/storage.go\nCREATE TABLE IF NOT EXISTS ${keyspace}.operation_throughput (\n    bucket        int,\n    ts            timeuuid,\n    throughput    text,\n    PRIMARY KEY(bucket, ts)\n) WITH CLUSTERING ORDER BY (ts desc);\n\nCREATE TABLE IF NOT EXISTS ${keyspace}.sampling_probabilities (\n    bucket        int,\n    ts            timeuuid,\n    hostname      text,\n    probabilities text,\n    PRIMARY KEY(bucket, ts)\n) WITH CLUSTERING ORDER BY (ts desc);\n\n-- distributed lock\n-- ./plugin/pkg/distributedlock/cassandra/lock.go\nCREATE TABLE IF NOT EXISTS ${keyspace}.leases (\n    name text,\n    owner text,\n    PRIMARY KEY (name)\n);"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/converter.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nconst (\n\t// warningStringPrefix is a magic string prefix for tag names to store span warnings.\n\twarningStringPrefix = \"$$span.warning.\"\n)\n\nvar (\n\tdbToDomainRefMap = map[string]model.SpanRefType{\n\t\tChildOf:     model.SpanRefType_CHILD_OF,\n\t\tFollowsFrom: model.SpanRefType_FOLLOWS_FROM,\n\t}\n\n\tdomainToDBRefMap = map[model.SpanRefType]string{\n\t\tmodel.SpanRefType_CHILD_OF:     ChildOf,\n\t\tmodel.SpanRefType_FOLLOWS_FROM: FollowsFrom,\n\t}\n\n\tdomainToDBValueTypeMap = map[model.ValueType]string{\n\t\tmodel.StringType:  StringType,\n\t\tmodel.BoolType:    BoolType,\n\t\tmodel.Int64Type:   Int64Type,\n\t\tmodel.Float64Type: Float64Type,\n\t\tmodel.BinaryType:  BinaryType,\n\t}\n)\n\n// FromDomain converts a domain model.Span to a database Span\nfunc FromDomain(span *model.Span) *Span {\n\treturn converter{}.fromDomain(span)\n}\n\n// ToDomain converts a database Span to a domain model.Span\nfunc ToDomain(dbSpan *Span) (*model.Span, error) {\n\treturn converter{}.toDomain(dbSpan)\n}\n\n// converter converts Spans between domain and database representations.\n// It primarily exists to namespace the conversion functions.\ntype converter struct{}\n\nfunc (c converter) fromDomain(span *model.Span) *Span {\n\ttags := c.toDBTags(span.Tags)\n\twarnings := c.toDBWarnings(span.Warnings)\n\tlogs := c.toDBLogs(span.Logs)\n\trefs := c.toDBRefs(span.References)\n\tudtProcess := c.toDBProcess(span.Process)\n\tspanHash, _ := model.HashCode(span)\n\n\ttags = append(tags, warnings...)\n\n\t//nolint:gosec // G115\n\treturn &Span{\n\t\tTraceID:       TraceIDFromDomain(span.TraceID),\n\t\tSpanID:        int64(span.SpanID),\n\t\tOperationName: span.OperationName,\n\t\tFlags:         int32(span.Flags),\n\t\tStartTime:     int64(model.TimeAsEpochMicroseconds(span.StartTime)),\n\t\tDuration:      int64(model.DurationAsMicroseconds(span.Duration)),\n\t\tTags:          tags,\n\t\tLogs:          logs,\n\t\tRefs:          refs,\n\t\tProcess:       udtProcess,\n\t\tServiceName:   span.Process.ServiceName,\n\t\tSpanHash:      int64(spanHash),\n\t}\n}\n\nfunc (c converter) toDomain(dbSpan *Span) (*model.Span, error) {\n\ttags, err := c.fromDBTags(dbSpan.Tags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\twarnings, err := c.fromDBWarnings(dbSpan.Tags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogs, err := c.fromDBLogs(dbSpan.Logs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trefs, err := c.fromDBRefs(dbSpan.Refs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprocess, err := c.fromDBProcess(dbSpan.Process)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttraceID := dbSpan.TraceID.ToDomain()\n\tspan := &model.Span{\n\t\tTraceID: traceID,\n\t\t//nolint:gosec // G115\n\t\tSpanID:        model.NewSpanID(uint64(dbSpan.SpanID)),\n\t\tOperationName: dbSpan.OperationName,\n\t\t//nolint:gosec // G115\n\t\tReferences: model.MaybeAddParentSpanID(traceID, model.NewSpanID(uint64(dbSpan.ParentID)), refs),\n\t\t//nolint:gosec // G115\n\t\tFlags: model.Flags(uint32(dbSpan.Flags)),\n\t\t//nolint:gosec // G115\n\t\tStartTime: model.EpochMicrosecondsAsTime(uint64(dbSpan.StartTime)),\n\t\t//nolint:gosec // G115\n\t\tDuration: model.MicrosecondsAsDuration(uint64(dbSpan.Duration)),\n\t\tTags:     tags,\n\t\tWarnings: warnings,\n\t\tLogs:     logs,\n\t\tProcess:  process,\n\t}\n\treturn span, nil\n}\n\nfunc (c converter) fromDBTags(tags []KeyValue) ([]model.KeyValue, error) {\n\tretMe := make([]model.KeyValue, 0, len(tags))\n\tfor i := range tags {\n\t\tif strings.HasPrefix(tags[i].Key, warningStringPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\tkv, err := c.fromDBTag(&tags[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tretMe = append(retMe, kv)\n\t}\n\treturn retMe, nil\n}\n\nfunc (c converter) fromDBWarnings(tags []KeyValue) ([]string, error) {\n\tvar retMe []string\n\tfor _, tag := range tags {\n\t\tif !strings.HasPrefix(tag.Key, warningStringPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\tkv, err := c.fromDBTag(&tag)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tretMe = append(retMe, kv.VStr)\n\t}\n\treturn retMe, nil\n}\n\nfunc (converter) fromDBTag(tag *KeyValue) (model.KeyValue, error) {\n\tswitch tag.ValueType {\n\tcase StringType:\n\t\treturn model.String(tag.Key, tag.ValueString), nil\n\tcase BoolType:\n\t\treturn model.Bool(tag.Key, tag.ValueBool), nil\n\tcase Int64Type:\n\t\treturn model.Int64(tag.Key, tag.ValueInt64), nil\n\tcase Float64Type:\n\t\treturn model.Float64(tag.Key, tag.ValueFloat64), nil\n\tcase BinaryType:\n\t\treturn model.Binary(tag.Key, tag.ValueBinary), nil\n\tdefault:\n\t\treturn model.KeyValue{}, fmt.Errorf(\"invalid ValueType in %+v\", tag)\n\t}\n}\n\nfunc (c converter) fromDBLogs(logs []Log) ([]model.Log, error) {\n\tretMe := make([]model.Log, len(logs))\n\tfor i, l := range logs {\n\t\tfields, err := c.fromDBTags(l.Fields)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tretMe[i] = model.Log{\n\t\t\t//nolint:gosec // G115\n\t\t\tTimestamp: model.EpochMicrosecondsAsTime(uint64(l.Timestamp)),\n\t\t\tFields:    fields,\n\t\t}\n\t}\n\treturn retMe, nil\n}\n\nfunc (converter) fromDBRefs(refs []SpanRef) ([]model.SpanRef, error) {\n\tretMe := make([]model.SpanRef, len(refs))\n\tfor i, r := range refs {\n\t\trefType, ok := dbToDomainRefMap[r.RefType]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"invalid SpanRefType in %+v\", r)\n\t\t}\n\t\tretMe[i] = model.SpanRef{\n\t\t\tRefType: refType,\n\t\t\tTraceID: r.TraceID.ToDomain(),\n\t\t\t//nolint:gosec // G115\n\t\t\tSpanID: model.NewSpanID(uint64(r.SpanID)),\n\t\t}\n\t}\n\treturn retMe, nil\n}\n\nfunc (c converter) fromDBProcess(process Process) (*model.Process, error) {\n\ttags, err := c.fromDBTags(process.Tags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &model.Process{\n\t\tTags:        tags,\n\t\tServiceName: process.ServiceName,\n\t}, nil\n}\n\nfunc (converter) toDBTags(tags []model.KeyValue) []KeyValue {\n\tretMe := make([]KeyValue, len(tags))\n\tfor i, t := range tags {\n\t\t// do we want to validate a jaeger tag here? Making sure that the type and value matches up?\n\t\tretMe[i] = KeyValue{\n\t\t\tKey:          t.Key,\n\t\t\tValueType:    domainToDBValueTypeMap[t.VType],\n\t\t\tValueString:  t.VStr,\n\t\t\tValueBool:    t.Bool(),\n\t\t\tValueInt64:   t.Int64(),\n\t\t\tValueFloat64: t.Float64(),\n\t\t\tValueBinary:  t.Binary(),\n\t\t}\n\t}\n\treturn retMe\n}\n\nfunc (converter) toDBWarnings(warnings []string) []KeyValue {\n\tretMe := make([]KeyValue, len(warnings))\n\tfor i, w := range warnings {\n\t\tkv := model.String(fmt.Sprintf(\"%s%d\", warningStringPrefix, i+1), w)\n\t\tretMe[i] = KeyValue{\n\t\t\tKey:         kv.Key,\n\t\t\tValueType:   domainToDBValueTypeMap[kv.VType],\n\t\t\tValueString: kv.VStr,\n\t\t}\n\t}\n\treturn retMe\n}\n\nfunc (c converter) toDBLogs(logs []model.Log) []Log {\n\tretMe := make([]Log, len(logs))\n\tfor i, l := range logs {\n\t\tretMe[i] = Log{\n\t\t\t//nolint:gosec // G115\n\t\t\tTimestamp: int64(model.TimeAsEpochMicroseconds(l.Timestamp)),\n\t\t\tFields:    c.toDBTags(l.Fields),\n\t\t}\n\t}\n\treturn retMe\n}\n\nfunc (converter) toDBRefs(refs []model.SpanRef) []SpanRef {\n\tretMe := make([]SpanRef, len(refs))\n\tfor i, r := range refs {\n\t\tretMe[i] = SpanRef{\n\t\t\tTraceID: TraceIDFromDomain(r.TraceID),\n\t\t\t//nolint:gosec // G115\n\t\t\tSpanID:  int64(r.SpanID),\n\t\t\tRefType: domainToDBRefMap[r.RefType],\n\t\t}\n\t}\n\treturn retMe\n}\n\nfunc (c converter) toDBProcess(process *model.Process) Process {\n\treturn Process{\n\t\tServiceName: process.ServiceName,\n\t\tTags:        c.toDBTags(process.Tags),\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/converter_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nvar (\n\tsomeTraceID       = model.NewTraceID(22222, 44444)\n\tsomeSpanID        = model.SpanID(3333)\n\tsomeParentSpanID  = model.SpanID(11111)\n\tsomeOperationName = \"someOperationName\"\n\tsomeStartTime     = model.EpochMicrosecondsAsTime(55555)\n\tsomeDuration      = model.MicrosecondsAsDuration(50000)\n\tsomeFlags         = model.Flags(1)\n\tsomeLogTimestamp  = model.EpochMicrosecondsAsTime(12345)\n\tsomeServiceName   = \"someServiceName\"\n\n\tsomeStringTagValue = \"someTagValue\"\n\tsomeBoolTagValue   = true\n\tsomeLongTagValue   = int64(123)\n\tsomeDoubleTagValue = float64(1.4)\n\tsomeBinaryTagValue = []byte(\"someBinaryValue\")\n\tsomeStringTagKey   = \"someStringTag\"\n\tsomeBoolTagKey     = \"someBoolTag\"\n\tsomeLongTagKey     = \"someLongTag\"\n\tsomeDoubleTagKey   = \"someDoubleTag\"\n\tsomeBinaryTagKey   = \"someBinaryTag\"\n\tsomeTags           = model.KeyValues{\n\t\tmodel.String(someStringTagKey, someStringTagValue),\n\t\tmodel.Bool(someBoolTagKey, someBoolTagValue),\n\t\tmodel.Int64(someLongTagKey, someLongTagValue),\n\t\tmodel.Float64(someDoubleTagKey, someDoubleTagValue),\n\t\tmodel.Binary(someBinaryTagKey, someBinaryTagValue),\n\t}\n\tsomeWarnings = []string{\"warning text 1\", \"warning text 2\"}\n\tsomeDBTags   = []KeyValue{\n\t\t{\n\t\t\tKey:         someStringTagKey,\n\t\t\tValueType:   StringType,\n\t\t\tValueString: someStringTagValue,\n\t\t},\n\t\t{\n\t\t\tKey:       someBoolTagKey,\n\t\t\tValueType: BoolType,\n\t\t\tValueBool: someBoolTagValue,\n\t\t},\n\t\t{\n\t\t\tKey:        someLongTagKey,\n\t\t\tValueType:  Int64Type,\n\t\t\tValueInt64: someLongTagValue,\n\t\t},\n\t\t{\n\t\t\tKey:          someDoubleTagKey,\n\t\t\tValueType:    Float64Type,\n\t\t\tValueFloat64: someDoubleTagValue,\n\t\t},\n\t\t{\n\t\t\tKey:         someBinaryTagKey,\n\t\t\tValueType:   BinaryType,\n\t\t\tValueBinary: someBinaryTagValue,\n\t\t},\n\t}\n\tsomeLogs = []model.Log{\n\t\t{\n\t\t\tTimestamp: someLogTimestamp,\n\t\t\tFields:    someTags,\n\t\t},\n\t}\n\tsomeDBLogs = []Log{\n\t\t{\n\t\t\tTimestamp: int64(model.TimeAsEpochMicroseconds(someLogTimestamp)),\n\t\t\tFields:    someDBTags,\n\t\t},\n\t}\n\tsomeRefs = []model.SpanRef{\n\t\t{\n\t\t\tTraceID: someTraceID,\n\t\t\tSpanID:  someParentSpanID,\n\t\t\tRefType: model.ChildOf,\n\t\t},\n\t}\n\tsomeDBProcess = Process{\n\t\tServiceName: someServiceName,\n\t\tTags:        someDBTags,\n\t}\n\tbadDBTags = []KeyValue{\n\t\t{\n\t\t\tKey:       \"sneh\",\n\t\t\tValueType: \"krustytheklown\",\n\t\t},\n\t}\n\tsomeDBTraceID = TraceIDFromDomain(someTraceID)\n\tsomeDBRefs    = []SpanRef{\n\t\t{\n\t\t\tRefType: \"child-of\",\n\t\t\tSpanID:  int64(someParentSpanID),\n\t\t\tTraceID: someDBTraceID,\n\t\t},\n\t}\n\tnotValidTagTypeErrStr = \"invalid ValueType in\"\n)\n\nfunc getTestJaegerSpan() *model.Span {\n\treturn &model.Span{\n\t\tTraceID:       someTraceID,\n\t\tSpanID:        someSpanID,\n\t\tOperationName: someOperationName,\n\t\tReferences:    someRefs,\n\t\tFlags:         someFlags,\n\t\tStartTime:     someStartTime,\n\t\tDuration:      someDuration,\n\t\tTags:          someTags,\n\t\tLogs:          someLogs,\n\t\tProcess:       getTestJaegerProcess(),\n\t}\n}\n\nfunc getTestJaegerProcess() *model.Process {\n\treturn &model.Process{\n\t\tServiceName: someServiceName,\n\t\tTags:        someTags,\n\t}\n}\n\nfunc getTestSpan() *Span {\n\tspan := &Span{\n\t\tTraceID:       someDBTraceID,\n\t\tSpanID:        int64(someSpanID),\n\t\tOperationName: someOperationName,\n\t\tFlags:         int32(someFlags),\n\t\tStartTime:     int64(model.TimeAsEpochMicroseconds(someStartTime)),\n\t\tDuration:      int64(model.DurationAsMicroseconds(someDuration)),\n\t\tTags:          someDBTags,\n\t\tLogs:          someDBLogs,\n\t\tRefs:          someDBRefs,\n\t\tProcess:       someDBProcess,\n\t\tServiceName:   someServiceName,\n\t}\n\t// there is no way to validate if the hash code is \"correct\" or not,\n\t// other than comparing it with some magic number that keeps changing\n\t// as the model changes. So let's just make sure the code is being\n\t// calculated during the conversion.\n\tspanHash, _ := model.HashCode(getTestJaegerSpan())\n\tspan.SpanHash = int64(spanHash)\n\treturn span\n}\n\nfunc getCustomSpan(dbTags []KeyValue, dbProcess Process, dbLogs []Log, dbRefs []SpanRef) *Span {\n\tspan := getTestSpan()\n\tspan.Tags = dbTags\n\tspan.Logs = dbLogs\n\tspan.Refs = dbRefs\n\tspan.Process = dbProcess\n\treturn span\n}\n\nfunc getTestUniqueTags() []TagInsertion {\n\treturn []TagInsertion{\n\t\t{ServiceName: \"someServiceName\", TagKey: \"someBoolTag\", TagValue: \"true\"},\n\t\t{ServiceName: \"someServiceName\", TagKey: \"someDoubleTag\", TagValue: \"1.4\"},\n\t\t{ServiceName: \"someServiceName\", TagKey: \"someLongTag\", TagValue: \"123\"},\n\t\t{ServiceName: \"someServiceName\", TagKey: \"someStringTag\", TagValue: \"someTagValue\"},\n\t}\n}\n\nfunc TestToSpan(t *testing.T) {\n\texpectedSpan := getTestSpan()\n\tactualDBSpan := FromDomain(getTestJaegerSpan())\n\tif !assert.Equal(t, expectedSpan, actualDBSpan) {\n\t\tfor _, diff := range pretty.Diff(expectedSpan, actualDBSpan) {\n\t\t\tt.Log(diff)\n\t\t}\n\t}\n}\n\nfunc TestFromSpan(t *testing.T) {\n\tfor _, testParentID := range []bool{false, true} {\n\t\ttestDBSpan := getTestSpan()\n\t\tif testParentID {\n\t\t\ttestDBSpan.ParentID = testDBSpan.Refs[0].SpanID\n\t\t\ttestDBSpan.Refs = nil\n\t\t}\n\t\texpectedSpan := getTestJaegerSpan()\n\t\tactualJSpan, err := ToDomain(testDBSpan)\n\t\trequire.NoError(t, err)\n\t\tif !assert.Equal(t, expectedSpan, actualJSpan) {\n\t\t\tfor _, diff := range pretty.Diff(expectedSpan, actualJSpan) {\n\t\t\t\tt.Log(diff)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestFailingFromDBSpanBadTags(t *testing.T) {\n\tfaultyDBTags := getCustomSpan(badDBTags, someDBProcess, someDBLogs, someDBRefs)\n\tfailingDBSpanTransform(t, faultyDBTags, notValidTagTypeErrStr)\n}\n\nfunc TestFailingFromDBSpanBadLogs(t *testing.T) {\n\tfaultyDBLogs := getCustomSpan(someDBTags, someDBProcess, []Log{\n\t\t{\n\t\t\tTimestamp: 0,\n\t\t\tFields:    badDBTags,\n\t\t},\n\t}, someDBRefs)\n\tfailingDBSpanTransform(t, faultyDBLogs, notValidTagTypeErrStr)\n}\n\nfunc TestFailingFromDBSpanBadProcess(t *testing.T) {\n\tfaultyDBProcess := getCustomSpan(someDBTags, Process{\n\t\tServiceName: someServiceName,\n\t\tTags:        badDBTags,\n\t}, someDBLogs, someDBRefs)\n\tfailingDBSpanTransform(t, faultyDBProcess, notValidTagTypeErrStr)\n}\n\nfunc TestFailingFromDBSpanBadRefs(t *testing.T) {\n\tfaultyDBRefs := getCustomSpan(someDBTags, someDBProcess, someDBLogs, []SpanRef{\n\t\t{\n\t\t\tRefType: \"makeOurOwnCasino\",\n\t\t\tTraceID: someDBTraceID,\n\t\t},\n\t})\n\tfailingDBSpanTransform(t, faultyDBRefs, \"invalid SpanRefType in\")\n}\n\nfunc failingDBSpanTransform(t *testing.T, dbSpan *Span, errMsg string) {\n\tjSpan, err := ToDomain(dbSpan)\n\tassert.Nil(t, jSpan)\n\tassert.ErrorContains(t, err, errMsg)\n}\n\nfunc TestFailingFromDBLogs(t *testing.T) {\n\tsomeDBLogs := []Log{\n\t\t{\n\t\t\tTimestamp: 0,\n\t\t\tFields: []KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:       \"sneh\",\n\t\t\t\t\tValueType: \"krustytheklown\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tjLogs, err := converter{}.fromDBLogs(someDBLogs)\n\tassert.Nil(t, jLogs)\n\tassert.ErrorContains(t, err, notValidTagTypeErrStr)\n}\n\nfunc TestDBTagTypeError(t *testing.T) {\n\t_, err := converter{}.fromDBTag(&KeyValue{ValueType: \"x\"})\n\tassert.ErrorContains(t, err, notValidTagTypeErrStr)\n}\n\nfunc TestGenerateHashCode(t *testing.T) {\n\tspan1 := getTestJaegerSpan()\n\tspan2 := getTestJaegerSpan()\n\thc1, err1 := model.HashCode(span1)\n\thc2, err2 := model.HashCode(span2)\n\tassert.Equal(t, hc1, hc2)\n\trequire.NoError(t, err1)\n\trequire.NoError(t, err2)\n\n\tspan2.Tags = append(span2.Tags, model.String(\"xyz\", \"some new tag\"))\n\thc2, err2 = model.HashCode(span2)\n\tassert.NotEqual(t, hc1, hc2)\n\trequire.NoError(t, err2)\n}\n\nfunc TestFromDBTagsWithoutWarnings(t *testing.T) {\n\tspan := getTestJaegerSpan()\n\tdbSpan := FromDomain(span)\n\n\ttags, err := converter{}.fromDBTags(dbSpan.Tags)\n\trequire.NoError(t, err)\n\tassert.Equal(t, tags, span.Tags)\n}\n\nfunc TestFromDBTagsWithWarnings(t *testing.T) {\n\tspan := getTestJaegerSpan()\n\tspan.Warnings = someWarnings\n\tdbSpan := FromDomain(span)\n\n\ttags, err := converter{}.fromDBTags(dbSpan.Tags)\n\trequire.NoError(t, err)\n\tassert.Equal(t, tags, span.Tags)\n}\n\nfunc TestFromDBLogsWithWarnings(t *testing.T) {\n\tspan := getTestJaegerSpan()\n\tspan.Warnings = someWarnings\n\tdbSpan := FromDomain(span)\n\n\tlogs, err := converter{}.fromDBLogs(dbSpan.Logs)\n\trequire.NoError(t, err)\n\tassert.Equal(t, logs, span.Logs)\n}\n\nfunc TestFromDBProcessWithWarnings(t *testing.T) {\n\tspan := getTestJaegerSpan()\n\tspan.Warnings = someWarnings\n\tdbSpan := FromDomain(span)\n\n\tprocess, err := converter{}.fromDBProcess(dbSpan.Process)\n\trequire.NoError(t, err)\n\tassert.Equal(t, process, span.Process)\n}\n\nfunc TestFromDBWarnings(t *testing.T) {\n\tspan := getTestJaegerSpan()\n\tspan.Warnings = someWarnings\n\tdbSpan := FromDomain(span)\n\n\twarnings, err := converter{}.fromDBWarnings(dbSpan.Tags)\n\trequire.NoError(t, err)\n\tassert.Equal(t, warnings, span.Warnings)\n}\n\nfunc TestFailingFromDBWarnings(t *testing.T) {\n\tbadDBWarningTags := []KeyValue{{Key: warningStringPrefix + \"1\", ValueType: \"invalidValueType\"}}\n\tspan := getCustomSpan(badDBWarningTags, someDBProcess, someDBLogs, someDBRefs)\n\tfailingDBSpanTransform(t, span, notValidTagTypeErrStr)\n}\n\nfunc TestFromDBTag_DefaultCase(t *testing.T) {\n\ttag := &KeyValue{\n\t\tKey:         \"test-key\",\n\t\tValueType:   \"unknown-type\",\n\t\tValueString: \"test-value\",\n\t}\n\n\tconverter := converter{}\n\tresult, err := converter.fromDBTag(tag)\n\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"invalid ValueType\")\n\tassert.Equal(t, model.KeyValue{}, result)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/cql_udt.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n)\n\n// ErrTraceIDWrongLength is an error that occurs when cassandra has a TraceID that's not 128 bits long\nvar ErrTraceIDWrongLength = errors.New(\"TraceID is not a 128bit integer\")\n\n// MarshalCQL handles marshaling DBTraceID (e.g. in SpanRef)\nfunc (t TraceID) MarshalCQL(gocql.TypeInfo) ([]byte, error) {\n\treturn t[:], nil\n}\n\n// UnmarshalCQL handles unmarshaling DBTraceID (e.g. in SpanRef)\nfunc (t *TraceID) UnmarshalCQL(_ gocql.TypeInfo, data []byte) error {\n\tif len(data) != 16 {\n\t\treturn ErrTraceIDWrongLength\n\t}\n\tcopy(t[:], data)\n\treturn nil\n}\n\n// MarshalUDT handles marshalling a Tag.\nfunc (t *KeyValue) MarshalUDT(name string, info gocql.TypeInfo) ([]byte, error) {\n\tswitch name {\n\tcase \"key\":\n\t\treturn gocql.Marshal(info, t.Key)\n\tcase \"value_type\":\n\t\treturn gocql.Marshal(info, t.ValueType)\n\tcase \"value_string\":\n\t\treturn gocql.Marshal(info, t.ValueString)\n\tcase \"value_bool\":\n\t\treturn gocql.Marshal(info, t.ValueBool)\n\tcase \"value_long\":\n\t\treturn gocql.Marshal(info, t.ValueInt64)\n\tcase \"value_double\":\n\t\treturn gocql.Marshal(info, t.ValueFloat64)\n\tcase \"value_binary\":\n\t\treturn gocql.Marshal(info, t.ValueBinary)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// UnmarshalUDT handles unmarshalling a Tag.\nfunc (t *KeyValue) UnmarshalUDT(name string, info gocql.TypeInfo, data []byte) error {\n\tswitch name {\n\tcase \"key\":\n\t\treturn gocql.Unmarshal(info, data, &t.Key)\n\tcase \"value_type\":\n\t\treturn gocql.Unmarshal(info, data, &t.ValueType)\n\tcase \"value_string\":\n\t\treturn gocql.Unmarshal(info, data, &t.ValueString)\n\tcase \"value_bool\":\n\t\treturn gocql.Unmarshal(info, data, &t.ValueBool)\n\tcase \"value_long\":\n\t\treturn gocql.Unmarshal(info, data, &t.ValueInt64)\n\tcase \"value_double\":\n\t\treturn gocql.Unmarshal(info, data, &t.ValueFloat64)\n\tcase \"value_binary\":\n\t\treturn gocql.Unmarshal(info, data, &t.ValueBinary)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// MarshalUDT handles marshalling a Log.\nfunc (l *Log) MarshalUDT(name string, info gocql.TypeInfo) ([]byte, error) {\n\tswitch name {\n\tcase \"ts\":\n\t\treturn gocql.Marshal(info, l.Timestamp)\n\tcase \"fields\":\n\t\treturn gocql.Marshal(info, l.Fields)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// UnmarshalUDT handles unmarshalling a Log.\nfunc (l *Log) UnmarshalUDT(name string, info gocql.TypeInfo, data []byte) error {\n\tswitch name {\n\tcase \"ts\":\n\t\treturn gocql.Unmarshal(info, data, &l.Timestamp)\n\tcase \"fields\":\n\t\treturn gocql.Unmarshal(info, data, &l.Fields)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// MarshalUDT handles marshalling a SpanRef.\nfunc (s *SpanRef) MarshalUDT(name string, info gocql.TypeInfo) ([]byte, error) {\n\tswitch name {\n\tcase \"ref_type\":\n\t\treturn gocql.Marshal(info, s.RefType)\n\tcase \"trace_id\":\n\t\treturn gocql.Marshal(info, s.TraceID)\n\tcase \"span_id\":\n\t\treturn gocql.Marshal(info, s.SpanID)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// UnmarshalUDT handles unmarshalling a SpanRef.\nfunc (s *SpanRef) UnmarshalUDT(name string, info gocql.TypeInfo, data []byte) error {\n\tswitch name {\n\tcase \"ref_type\":\n\t\treturn gocql.Unmarshal(info, data, &s.RefType)\n\tcase \"trace_id\":\n\t\treturn gocql.Unmarshal(info, data, &s.TraceID)\n\tcase \"span_id\":\n\t\treturn gocql.Unmarshal(info, data, &s.SpanID)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// MarshalUDT handles marshalling a Process.\nfunc (p *Process) MarshalUDT(name string, info gocql.TypeInfo) ([]byte, error) {\n\tswitch name {\n\tcase \"service_name\":\n\t\treturn gocql.Marshal(info, p.ServiceName)\n\tcase \"tags\":\n\t\treturn gocql.Marshal(info, p.Tags)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n\n// UnmarshalUDT handles unmarshalling a Process.\nfunc (p *Process) UnmarshalUDT(name string, info gocql.TypeInfo, data []byte) error {\n\tswitch name {\n\tcase \"service_name\":\n\t\treturn gocql.Unmarshal(info, data, &p.ServiceName)\n\tcase \"tags\":\n\t\treturn gocql.Unmarshal(info, data, &p.Tags)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown column for position: %q\", name)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/cql_udt_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"encoding/binary\"\n\t\"math\"\n\t\"testing\"\n\n\tgocql \"github.com/apache/cassandra-gocql-driver/v2\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/gocql/testutils\"\n)\n\nfunc TestDBModelUDTMarshall(t *testing.T) {\n\tfloatVal := float64(123.456)\n\tfloatBytes := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(floatBytes, uint64(math.Float64bits(floatVal)))\n\n\tkv := &KeyValue{\n\t\tKey:          \"key\",\n\t\tValueType:    \"value_type\",\n\t\tValueString:  \"string_value\",\n\t\tValueBool:    true,\n\t\tValueInt64:   123,\n\t\tValueFloat64: floatVal,\n\t\tValueBinary:  []byte(\"value_binary\"),\n\t}\n\n\tlog := &Log{\n\t\tTimestamp: 123,\n\t}\n\n\tspanRef := &SpanRef{\n\t\tRefType: \"childOf\",\n\t\tTraceID: TraceIDFromDomain(model.NewTraceID(0, 1)),\n\t\tSpanID:  123,\n\t}\n\n\tproc := &Process{\n\t\tServiceName: \"google\",\n\t}\n\n\ttestCases := []testutils.UDTTestCase{\n\t\t{\n\t\t\tObj:     kv,\n\t\t\tNew:     func() gocql.UDTUnmarshaler { return &KeyValue{} },\n\t\t\tObjName: \"KeyValue\",\n\t\t\tFields: []testutils.UDTField{\n\t\t\t\t{Name: \"key\", Type: gocql.TypeAscii, ValIn: []byte(\"key\"), Err: false},\n\t\t\t\t{Name: \"value_type\", Type: gocql.TypeAscii, ValIn: []byte(\"value_type\"), Err: false},\n\t\t\t\t{Name: \"value_string\", Type: gocql.TypeAscii, ValIn: []byte(\"string_value\"), Err: false},\n\t\t\t\t{Name: \"value_bool\", Type: gocql.TypeBoolean, ValIn: []byte{1}, Err: false},\n\t\t\t\t{Name: \"value_long\", Type: gocql.TypeBigInt, ValIn: []byte{0, 0, 0, 0, 0, 0, 0, 123}, Err: false},\n\t\t\t\t{Name: \"value_double\", Type: gocql.TypeDouble, ValIn: floatBytes, Err: false},\n\t\t\t\t{Name: \"value_binary\", Type: gocql.TypeBlob, ValIn: []byte(\"value_binary\"), Err: false},\n\t\t\t\t{Name: \"wrong-field\", Err: true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObj:     log,\n\t\t\tNew:     func() gocql.UDTUnmarshaler { return &Log{} },\n\t\t\tObjName: \"Log\",\n\t\t\tFields: []testutils.UDTField{\n\t\t\t\t{Name: \"ts\", Type: gocql.TypeBigInt, ValIn: []byte{0, 0, 0, 0, 0, 0, 0, 123}, Err: false},\n\t\t\t\t// Wrong type TypeAscii for an array field\n\t\t\t\t{Name: \"fields\", Type: gocql.TypeAscii, ValIn: []byte{}, Err: true},\n\t\t\t\t{Name: \"wrong-field\", Err: true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObj:     spanRef,\n\t\t\tNew:     func() gocql.UDTUnmarshaler { return &SpanRef{} },\n\t\t\tObjName: \"SpanRef\",\n\t\t\tFields: []testutils.UDTField{\n\t\t\t\t{Name: \"ref_type\", Type: gocql.TypeAscii, ValIn: []byte(\"childOf\"), Err: false},\n\t\t\t\t{Name: \"trace_id\", Type: gocql.TypeBlob, ValIn: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Err: false},\n\t\t\t\t{Name: \"span_id\", Type: gocql.TypeBigInt, ValIn: []byte{0, 0, 0, 0, 0, 0, 0, 123}, Err: false},\n\t\t\t\t{Name: \"wrong-field\", Err: true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObj:     proc,\n\t\t\tNew:     func() gocql.UDTUnmarshaler { return &Process{} },\n\t\t\tObjName: \"Process\",\n\t\t\tFields: []testutils.UDTField{\n\t\t\t\t{Name: \"service_name\", Type: gocql.TypeAscii, ValIn: []byte(\"google\"), Err: false},\n\t\t\t\t// Wrong type TypeAscii for an array field\n\t\t\t\t{Name: \"tags\", Type: gocql.TypeAscii, ValIn: []byte{}, Err: true},\n\t\t\t\t{Name: \"wrong-field\", Err: true},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\ttestCase.Run(t)\n\t}\n\n\tdbid := TraceID{}\n\tassert.Equal(t, ErrTraceIDWrongLength, dbid.UnmarshalCQL(nil, nil))\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/ids.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// TraceID is a serializable form of model.TraceID\ntype TraceID [16]byte\n\n// ToDomain converts trace ID from db-serializable form to domain TradeID\nfunc (t TraceID) ToDomain() model.TraceID {\n\ttraceIDHigh := binary.BigEndian.Uint64(t[:8])\n\ttraceIDLow := binary.BigEndian.Uint64(t[8:])\n\treturn model.NewTraceID(traceIDHigh, traceIDLow)\n}\n\n// String returns hex string representation of the trace ID.\nfunc (t TraceID) String() string {\n\ttraceIDHigh := binary.BigEndian.Uint64(t[:8])\n\ttraceIDLow := binary.BigEndian.Uint64(t[8:])\n\tif traceIDHigh == 0 {\n\t\treturn fmt.Sprintf(\"%016x\", traceIDLow)\n\t}\n\treturn fmt.Sprintf(\"%016x%016x\", traceIDHigh, traceIDLow)\n}\n\n// MarshalJSON converts trace id into a base64 string enclosed in quotes.\nfunc (t TraceID) MarshalJSON() ([]byte, error) {\n\tvar out [26]byte\n\tout[0] = '\"'\n\tbase64.StdEncoding.Encode(out[1:25], t[:])\n\tout[25] = '\"'\n\treturn out[:], nil\n}\n\nfunc (t *TraceID) UnmarshalJSON(data []byte) error {\n\tvar s string\n\tif err := json.Unmarshal(data, &s); err != nil {\n\t\treturn err\n\t}\n\tb, err := base64.StdEncoding.DecodeString(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(b) != 16 {\n\t\treturn fmt.Errorf(\"invalid TraceID length: %d\", len(b))\n\t}\n\tcopy(t[:], b)\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/ids_test.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTraceIDJSONRoundTrip(t *testing.T) {\n\toriginal := TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}\n\tb, err := json.Marshal(original)\n\trequire.NoError(t, err)\n\texpectedStr := \"\\\"AAAAAAAAAAAAAAAAAAAAAQ==\\\"\"\n\tassert.Equal(t, expectedStr, string(b))\n\tvar decoded TraceID\n\terr = json.Unmarshal(b, &decoded)\n\trequire.NoError(t, err)\n\tassert.Equal(t, original, decoded)\n}\n\nfunc TestTraceIDJSONUnmarshal_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\terr   string\n\t}{\n\t\t{\n\t\t\tname:  \"not a JSON string\",\n\t\t\tinput: `123`,\n\t\t\terr:   \"json: cannot unmarshal number into Go value of type string\",\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid base64\",\n\t\t\tinput: `\"@@@@\"`,\n\t\t\terr:   \"illegal base64 data at input byte 0\",\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid decoded length\",\n\t\t\tinput: `\"AQ==\"`,\n\t\t\terr:   \"invalid TraceID length: 1\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar traceId TraceID\n\t\t\terr := json.Unmarshal([]byte(tt.input), &traceId)\n\t\t\tassert.ErrorContains(t, err, tt.err)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/index_filter.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nconst (\n\t// DurationIndex represents the flag for indexing by duration.\n\tDurationIndex = iota\n\n\t// ServiceIndex represents the flag for indexing by service.\n\tServiceIndex\n\n\t// OperationIndex represents the flag for indexing by service-operation.\n\tOperationIndex\n)\n\n// IndexFilter filters out any spans that should not be indexed depending on the index specified.\ntype IndexFilter func(span *Span, index int) bool\n\n// DefaultIndexFilter is a filter that indexes everything.\nvar DefaultIndexFilter = func(_ *Span, _ /* index */ int) bool {\n\treturn true\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/index_filter_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDefaultIndexFilter(t *testing.T) {\n\tspan := &Span{}\n\tfilter := DefaultIndexFilter\n\tassert.True(t, filter(span, DurationIndex))\n\tassert.True(t, filter(span, ServiceIndex))\n\tassert.True(t, filter(span, OperationIndex))\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/model.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nconst (\n\tChildOf     = \"child-of\"\n\tFollowsFrom = \"follows-from\"\n\n\tStringType  = \"string\"\n\tBoolType    = \"bool\"\n\tInt64Type   = \"int64\"\n\tFloat64Type = \"float64\"\n\tBinaryType  = \"binary\"\n)\n\n// Span is the database representation of a span.\ntype Span struct {\n\tTraceID       TraceID\n\tSpanID        int64\n\tParentID      int64 // deprecated\n\tOperationName string\n\tFlags         int32\n\tStartTime     int64 // microseconds since epoch\n\tDuration      int64 // microseconds\n\tTags          []KeyValue\n\tLogs          []Log\n\tRefs          []SpanRef\n\tProcess       Process\n\tServiceName   string\n\tSpanHash      int64\n}\n\n// KeyValue is the UDT representation of a Jaeger KeyValue.\ntype KeyValue struct {\n\tKey          string  `cql:\"key\"`\n\tValueType    string  `cql:\"value_type\"`\n\tValueString  string  `cql:\"value_string\" json:\"value_string,omitempty\"`\n\tValueBool    bool    `cql:\"value_bool\" json:\"value_bool,omitempty\"`\n\tValueInt64   int64   `cql:\"value_long\" json:\"value_long,omitempty\"`     // using more natural column name for Cassandra\n\tValueFloat64 float64 `cql:\"value_double\" json:\"value_double,omitempty\"` // using more natural column name for Cassandra\n\tValueBinary  []byte  `cql:\"value_binary\" json:\"value_binary,omitempty\"`\n}\n\nfunc (t *KeyValue) compareValues(that *KeyValue) int {\n\tswitch t.ValueType {\n\tcase StringType:\n\t\treturn strings.Compare(t.ValueString, that.ValueString)\n\tcase BoolType:\n\t\tif t.ValueBool != that.ValueBool {\n\t\t\tif !t.ValueBool {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t\treturn 1\n\t\t}\n\tcase Int64Type:\n\t\treturn int(t.ValueInt64 - that.ValueInt64)\n\tcase Float64Type:\n\t\tif t.ValueFloat64 != that.ValueFloat64 {\n\t\t\tif t.ValueFloat64 < that.ValueFloat64 {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t\treturn 1\n\t\t}\n\tcase BinaryType:\n\t\treturn bytes.Compare(t.ValueBinary, that.ValueBinary)\n\tdefault:\n\t\treturn -1 // theoretical case, not stating them equal but placing the base pointer before other\n\t}\n\treturn 0\n}\n\nfunc (t *KeyValue) Compare(that any) int {\n\tif that == nil {\n\t\tif t == nil {\n\t\t\treturn 0\n\t\t}\n\t\treturn 1\n\t}\n\tthat1, ok := that.(*KeyValue)\n\tif !ok {\n\t\tthat2, ok := that.(KeyValue)\n\t\tif !ok {\n\t\t\treturn 1\n\t\t}\n\t\tthat1 = &that2\n\t}\n\tif that1 == nil {\n\t\tif t == nil {\n\t\t\treturn 0\n\t\t}\n\t\treturn 1\n\t} else if t == nil {\n\t\treturn -1\n\t}\n\tif cmp := strings.Compare(t.Key, that1.Key); cmp != 0 {\n\t\treturn cmp\n\t}\n\tif cmp := strings.Compare(t.ValueType, that1.ValueType); cmp != 0 {\n\t\treturn cmp\n\t}\n\treturn t.compareValues(that1)\n}\n\nfunc (t *KeyValue) Equal(that any) bool {\n\treturn t.Compare(that) == 0\n}\n\nfunc (t *KeyValue) AsString() string {\n\tswitch t.ValueType {\n\tcase StringType:\n\t\treturn t.ValueString\n\tcase BoolType:\n\t\tif t.ValueBool {\n\t\t\treturn \"true\"\n\t\t}\n\t\treturn \"false\"\n\tcase Int64Type:\n\t\treturn strconv.FormatInt(t.ValueInt64, 10)\n\tcase Float64Type:\n\t\treturn strconv.FormatFloat(t.ValueFloat64, 'g', 10, 64)\n\tcase BinaryType:\n\t\treturn hex.EncodeToString(t.ValueBinary)\n\tdefault:\n\t\treturn \"unknown type \" + t.ValueType\n\t}\n}\n\nfunc SortKVs(kvs []KeyValue) {\n\tsort.Slice(kvs, func(i, j int) bool {\n\t\treturn kvs[i].Compare(kvs[j]) < 0\n\t})\n}\n\n// Log is the UDT representation of a Jaeger Log.\ntype Log struct {\n\tTimestamp int64      `cql:\"ts\"` // microseconds since epoch\n\tFields    []KeyValue `cql:\"fields\"`\n}\n\n// SpanRef is the UDT representation of a Jaeger Span Reference.\ntype SpanRef struct {\n\tRefType string  `cql:\"ref_type\"`\n\tTraceID TraceID `cql:\"trace_id\"`\n\tSpanID  int64   `cql:\"span_id\"`\n}\n\n// Process is the UDT representation of a Jaeger Process.\ntype Process struct {\n\tServiceName string     `cql:\"service_name\"`\n\tTags        []KeyValue `cql:\"tags\"`\n}\n\n// TagInsertion contains the items necessary to insert a tag for a given span\ntype TagInsertion struct {\n\tServiceName string\n\tTagKey      string\n\tTagValue    string\n}\n\nfunc (t TagInsertion) String() string {\n\tconst uniqueTagDelimiter = \":\"\n\tvar buffer bytes.Buffer\n\tbuffer.WriteString(t.ServiceName)\n\tbuffer.WriteString(uniqueTagDelimiter)\n\tbuffer.WriteString(t.TagKey)\n\tbuffer.WriteString(uniqueTagDelimiter)\n\tbuffer.WriteString(t.TagValue)\n\treturn buffer.String()\n}\n\n// TraceIDFromDomain converts domain TraceID into serializable DB representation.\nfunc TraceIDFromDomain(traceID model.TraceID) TraceID {\n\tdbTraceID := TraceID{}\n\tbinary.BigEndian.PutUint64(dbTraceID[:8], uint64(traceID.High))\n\tbinary.BigEndian.PutUint64(dbTraceID[8:], uint64(traceID.Low))\n\treturn dbTraceID\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/model_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nfunc TestTagInsertionString(t *testing.T) {\n\tv := TagInsertion{\"x\", \"y\", \"z\"}\n\tassert.Equal(t, \"x:y:z\", v.String())\n}\n\nfunc TestTraceIDString(t *testing.T) {\n\tid := TraceIDFromDomain(model.NewTraceID(1, 1))\n\tassert.Equal(t, \"00000000000000010000000000000001\", id.String())\n}\n\nfunc TestKeyValueCompare(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tkv1    *KeyValue\n\t\tkv2    any\n\t\tresult int\n\t}{\n\t\t{\n\t\t\tname:   \"BothNil\",\n\t\t\tkv1:    nil,\n\t\t\tkv2:    nil,\n\t\t\tresult: 0,\n\t\t},\n\t\t{\n\t\t\tname:   \"Nil_vs_Value\",\n\t\t\tkv1:    nil,\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tresult: -1,\n\t\t},\n\t\t{\n\t\t\tname:   \"Pointer_vs_Value\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    KeyValue{Key: \"m\", ValueType: \"string\"},\n\t\t\tresult: -1,\n\t\t},\n\t\t{\n\t\t\tname:   \"Value_vs_Nil\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    nil,\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"TypedNil_vs_Value\",\n\t\t\tkv1:    (*KeyValue)(nil),\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tresult: -1,\n\t\t},\n\t\t{\n\t\t\tname:   \"TypedNil_vs_TypedNil\",\n\t\t\tkv1:    (*KeyValue)(nil),\n\t\t\tkv2:    (*KeyValue)(nil),\n\t\t\tresult: 0,\n\t\t},\n\t\t{\n\t\t\tname:   \"Value_vs_TypedNil\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    (*KeyValue)(nil),\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"InvalidType\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    123,\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"Equal\",\n\t\t\tkv1: &KeyValue{\n\t\t\t\tKey:         \"k\",\n\t\t\t\tValueType:   \"string\",\n\t\t\t\tValueString: \"hello\",\n\t\t\t},\n\t\t\tkv2: &KeyValue{\n\t\t\t\tKey:         \"k\",\n\t\t\t\tValueType:   \"string\",\n\t\t\t\tValueString: \"hello\",\n\t\t\t},\n\t\t\tresult: 0,\n\t\t},\n\t\t{\n\t\t\tname:   \"KeyMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    &KeyValue{Key: \"a\", ValueType: \"string\"},\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueTypeMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"z\"},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"a\"},\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueStringMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\", ValueString: \"zzz\"},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"string\", ValueString: \"aaa\"},\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBoolMismatch_After\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"bool\", ValueBool: true},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"bool\", ValueBool: false},\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBoolMismatch_Before\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"bool\", ValueBool: false},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"bool\", ValueBool: true},\n\t\t\tresult: -1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueInt64Mismatch_After\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"int64\", ValueInt64: 10},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"int64\", ValueInt64: 5},\n\t\t\tresult: 5,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueFloat64Mismatch_After\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"float64\", ValueFloat64: 1.5},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"float64\", ValueFloat64: 0.5},\n\t\t\tresult: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueFloat64Mismatch_Before\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"float64\", ValueFloat64: 0.5},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"float64\", ValueFloat64: 1.5},\n\t\t\tresult: -1,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBinaryMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 4}},\n\t\t\tresult: bytes.Compare([]byte{1, 2, 3}, []byte{1, 2, 4}),\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBinaryEqual\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t\tresult: 0,\n\t\t},\n\t\t{\n\t\t\tname:   \"UnknownType\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"random\", ValueString: \"hello\"},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"random\", ValueString: \"hellobig\"},\n\t\t\tresult: -1,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.result, tc.kv1.Compare(tc.kv2))\n\t\t})\n\t}\n}\n\nfunc TestKeyValueEqual(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tkv1    *KeyValue\n\t\tkv2    any\n\t\tresult bool\n\t}{\n\t\t{\n\t\t\tname:   \"BothNil\",\n\t\t\tkv1:    nil,\n\t\t\tkv2:    nil,\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\tname:   \"Nil_vs_Value\",\n\t\t\tkv1:    nil,\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"Value_vs_Nil\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    nil,\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"TypedNil_vs_Value\",\n\t\t\tkv1:    (*KeyValue)(nil),\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"Value_vs_TypedNil\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    (*KeyValue)(nil),\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"InvalidType\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    123,\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Equal\",\n\t\t\tkv1: &KeyValue{\n\t\t\t\tKey:         \"k\",\n\t\t\t\tValueType:   \"string\",\n\t\t\t\tValueString: \"hello\",\n\t\t\t},\n\t\t\tkv2: &KeyValue{\n\t\t\t\tKey:         \"k\",\n\t\t\t\tValueType:   \"string\",\n\t\t\t\tValueString: \"hello\",\n\t\t\t},\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\tname:   \"KeyMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\"},\n\t\t\tkv2:    &KeyValue{Key: \"a\", ValueType: \"string\"},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueTypeMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"z\"},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"a\"},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueStringMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"string\", ValueString: \"zzz\"},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"string\", ValueString: \"aaa\"},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBoolMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"bool\", ValueBool: true},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"bool\", ValueBool: false},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueInt64Mismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"int64\", ValueInt64: 10},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"int64\", ValueInt64: 5},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueFloat64Mismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"float64\", ValueFloat64: 1.5},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"float64\", ValueFloat64: 0.5},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBinaryMismatch\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 4}},\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"ValueBinaryEqual\",\n\t\t\tkv1:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t\tkv2:    &KeyValue{Key: \"k\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t\tresult: true,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.result, tc.kv1.Equal(tc.kv2))\n\t\t})\n\t}\n}\n\nfunc TestKeyValueAsString(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tkv     KeyValue\n\t\texpect string\n\t}{\n\t\t{\n\t\t\tname: \"StringType\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:         \"k\",\n\t\t\t\tValueType:   StringType,\n\t\t\t\tValueString: \"hello\",\n\t\t\t},\n\t\t\texpect: \"hello\",\n\t\t},\n\t\t{\n\t\t\tname: \"BoolTrue\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:       \"k\",\n\t\t\t\tValueType: BoolType,\n\t\t\t\tValueBool: true,\n\t\t\t},\n\t\t\texpect: \"true\",\n\t\t},\n\t\t{\n\t\t\tname: \"BoolFalse\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:       \"k\",\n\t\t\t\tValueType: BoolType,\n\t\t\t\tValueBool: false,\n\t\t\t},\n\t\t\texpect: \"false\",\n\t\t},\n\t\t{\n\t\t\tname: \"Int64Type\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:        \"k\",\n\t\t\t\tValueType:  Int64Type,\n\t\t\t\tValueInt64: 12345,\n\t\t\t},\n\t\t\texpect: \"12345\",\n\t\t},\n\t\t{\n\t\t\tname: \"Float64Type\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:          \"k\",\n\t\t\t\tValueType:    Float64Type,\n\t\t\t\tValueFloat64: 12.34,\n\t\t\t},\n\t\t\texpect: \"12.34\",\n\t\t},\n\t\t{\n\t\t\tname: \"BinaryType\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:         \"k\",\n\t\t\t\tValueType:   BinaryType,\n\t\t\t\tValueBinary: []byte{0xAB, 0xCD, 0xEF},\n\t\t\t},\n\t\t\texpect: \"abcdef\",\n\t\t},\n\t\t{\n\t\t\tname: \"UnknownType\",\n\t\t\tkv: KeyValue{\n\t\t\t\tKey:       \"k\",\n\t\t\t\tValueType: \"random-type\",\n\t\t\t},\n\t\t\texpect: \"unknown type random-type\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.expect, tc.kv.AsString())\n\t\t})\n\t}\n}\n\nfunc TestSortKVs_WithKey(t *testing.T) {\n\tkvs := []KeyValue{\n\t\t{Key: \"z\", ValueType: \"string\", ValueString: \"hello\"},\n\t\t{Key: \"y\", ValueType: \"bool\", ValueBool: true},\n\t\t{Key: \"x\", ValueType: \"int64\", ValueInt64: 99},\n\t\t{Key: \"w\", ValueType: \"double\", ValueFloat64: 1.23},\n\t\t{Key: \"v\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"abc\"},\n\t}\n\tSortKVs(kvs)\n\twant := []string{\"m\", \"v\", \"w\", \"x\", \"y\", \"z\"}\n\tfor i, kv := range kvs {\n\t\tassert.Equal(t, want[i], kv.Key)\n\t}\n}\n\nfunc TestSortKVs_WithType(t *testing.T) {\n\tkvs := []KeyValue{\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"hello\"},\n\t\t{Key: \"m\", ValueType: \"bool\", ValueBool: true},\n\t\t{Key: \"m\", ValueType: \"int64\", ValueInt64: 99},\n\t\t{Key: \"m\", ValueType: \"double\", ValueFloat64: 1.23},\n\t\t{Key: \"m\", ValueType: \"binary\", ValueBinary: []byte{1, 2, 3}},\n\t}\n\tSortKVs(kvs)\n\twant := []string{\"binary\", \"bool\", \"double\", \"int64\", \"string\"}\n\tfor i, kv := range kvs {\n\t\tassert.Equal(t, want[i], kv.ValueType)\n\t}\n}\n\nfunc TestSortKVs_WithValue(t *testing.T) {\n\tkvs := []KeyValue{\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"a\"},\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"b\"},\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"c\"},\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"d\"},\n\t\t{Key: \"m\", ValueType: \"string\", ValueString: \"e\"},\n\t}\n\tSortKVs(kvs)\n\twant := []string{\"a\", \"b\", \"c\", \"d\", \"e\"}\n\tfor i, kv := range kvs {\n\t\tassert.Equal(t, want[i], kv.ValueString)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/operation.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// Operation defines schema for records saved in operation_names_v2 table\ntype Operation struct {\n\tServiceName   string\n\tSpanKind      string\n\tOperationName string\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/tag_filter.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// TagFilter filters out any tags that should not be indexed.\ntype TagFilter interface {\n\tFilterProcessTags(span *Span, processTags []KeyValue) []KeyValue\n\tFilterTags(span *Span, tags []KeyValue) []KeyValue\n\tFilterLogFields(span *Span, logFields []KeyValue) []KeyValue\n}\n\n// ChainedTagFilter applies multiple tag filters in serial fashion.\ntype ChainedTagFilter []TagFilter\n\n// NewChainedTagFilter creates a TagFilter from the variadic list of passed TagFilter.\nfunc NewChainedTagFilter(filters ...TagFilter) ChainedTagFilter {\n\treturn filters\n}\n\n// FilterProcessTags calls each FilterProcessTags.\nfunc (tf ChainedTagFilter) FilterProcessTags(span *Span, processTags []KeyValue) []KeyValue {\n\tfor _, f := range tf {\n\t\tprocessTags = f.FilterProcessTags(span, processTags)\n\t}\n\treturn processTags\n}\n\n// FilterTags calls each FilterTags\nfunc (tf ChainedTagFilter) FilterTags(span *Span, tags []KeyValue) []KeyValue {\n\tfor _, f := range tf {\n\t\ttags = f.FilterTags(span, tags)\n\t}\n\treturn tags\n}\n\n// FilterLogFields calls each FilterLogFields\nfunc (tf ChainedTagFilter) FilterLogFields(span *Span, logFields []KeyValue) []KeyValue {\n\tfor _, f := range tf {\n\t\tlogFields = f.FilterLogFields(span, logFields)\n\t}\n\treturn logFields\n}\n\n// DefaultTagFilter returns a filter that retrieves all tags from span.Tags, span.Logs, and span.Process.\nvar DefaultTagFilter = tagFilterImpl{}\n\ntype tagFilterImpl struct{}\n\nfunc (tagFilterImpl) FilterProcessTags(_ *Span, processTags []KeyValue) []KeyValue {\n\treturn processTags\n}\n\nfunc (tagFilterImpl) FilterTags(_ *Span, tags []KeyValue) []KeyValue {\n\treturn tags\n}\n\nfunc (tagFilterImpl) FilterLogFields(_ *Span, logFields []KeyValue) []KeyValue {\n\treturn logFields\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/tag_filter_drop_all.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// TagFilterDropAll filters all fields of a given type.\ntype TagFilterDropAll struct {\n\tdropTags        bool\n\tdropProcessTags bool\n\tdropLogs        bool\n}\n\n// NewTagFilterDropAll return a filter that filters all of the specified type\nfunc NewTagFilterDropAll(dropTags bool, dropProcessTags bool, dropLogs bool) *TagFilterDropAll {\n\treturn &TagFilterDropAll{\n\t\tdropTags:        dropTags,\n\t\tdropProcessTags: dropProcessTags,\n\t\tdropLogs:        dropLogs,\n\t}\n}\n\n// FilterProcessTags implements TagFilter\nfunc (f *TagFilterDropAll) FilterProcessTags(_ *Span, processTags []KeyValue) []KeyValue {\n\tif f.dropProcessTags {\n\t\treturn []KeyValue{}\n\t}\n\treturn processTags\n}\n\n// FilterTags implements TagFilter\nfunc (f *TagFilterDropAll) FilterTags(_ *Span, tags []KeyValue) []KeyValue {\n\tif f.dropTags {\n\t\treturn []KeyValue{}\n\t}\n\treturn tags\n}\n\n// FilterLogFields implements TagFilter\nfunc (f *TagFilterDropAll) FilterLogFields(_ *Span, logFields []KeyValue) []KeyValue {\n\tif f.dropLogs {\n\t\treturn []KeyValue{}\n\t}\n\treturn logFields\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/tag_filter_drop_all_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ TagFilter = &TagFilterDropAll{} // Check API compliance\n\nfunc TestDropAll(t *testing.T) {\n\ttt := []struct {\n\t\tfilter              *TagFilterDropAll\n\t\texpectedTags        []KeyValue\n\t\texpectedProcessTags []KeyValue\n\t\texpectedLogs        []KeyValue\n\t}{\n\t\t{\n\t\t\tfilter:              NewTagFilterDropAll(false, false, false),\n\t\t\texpectedTags:        someDBTags,\n\t\t\texpectedProcessTags: someDBTags,\n\t\t\texpectedLogs:        someDBTags,\n\t\t},\n\t\t{\n\t\t\tfilter:              NewTagFilterDropAll(true, false, false),\n\t\t\texpectedTags:        []KeyValue{},\n\t\t\texpectedProcessTags: someDBTags,\n\t\t\texpectedLogs:        someDBTags,\n\t\t},\n\t\t{\n\t\t\tfilter:              NewTagFilterDropAll(false, true, false),\n\t\t\texpectedTags:        someDBTags,\n\t\t\texpectedProcessTags: []KeyValue{},\n\t\t\texpectedLogs:        someDBTags,\n\t\t},\n\t\t{\n\t\t\tfilter:              NewTagFilterDropAll(false, false, true),\n\t\t\texpectedTags:        someDBTags,\n\t\t\texpectedProcessTags: someDBTags,\n\t\t\texpectedLogs:        []KeyValue{},\n\t\t},\n\t\t{\n\t\t\tfilter:              NewTagFilterDropAll(true, false, true),\n\t\t\texpectedTags:        []KeyValue{},\n\t\t\texpectedProcessTags: someDBTags,\n\t\t\texpectedLogs:        []KeyValue{},\n\t\t},\n\t\t{\n\t\t\tfilter:              NewTagFilterDropAll(true, true, true),\n\t\t\texpectedTags:        []KeyValue{},\n\t\t\texpectedProcessTags: []KeyValue{},\n\t\t\texpectedLogs:        []KeyValue{},\n\t\t},\n\t}\n\n\tfor _, test := range tt {\n\t\tactualTags := test.filter.FilterTags(nil, someDBTags)\n\t\tassert.Equal(t, test.expectedTags, actualTags)\n\n\t\tactualProcessTags := test.filter.FilterProcessTags(nil, someDBTags)\n\t\tassert.Equal(t, test.expectedProcessTags, actualProcessTags)\n\n\t\tactualLogs := test.filter.FilterLogFields(nil, someDBTags)\n\t\tassert.Equal(t, test.expectedLogs, actualLogs)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/tag_filter_exact_match.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// ExactMatchTagFilter filters out all tags in its tags slice\ntype ExactMatchTagFilter struct {\n\ttags        map[string]struct{}\n\tdropMatches bool\n}\n\n// newExactMatchTagFilter creates a ExactMatchTagFilter with the provided tags.  Passing\n// dropMatches true will exhibit blacklist behavior.  Passing dropMatches false\n// will exhibit whitelist behavior.\nfunc newExactMatchTagFilter(tags []string, dropMatches bool) ExactMatchTagFilter {\n\tmapTags := make(map[string]struct{})\n\tfor _, t := range tags {\n\t\tmapTags[t] = struct{}{}\n\t}\n\treturn ExactMatchTagFilter{\n\t\ttags:        mapTags,\n\t\tdropMatches: dropMatches,\n\t}\n}\n\n// NewBlacklistFilter is a convenience method for creating a blacklist ExactMatchTagFilter\nfunc NewBlacklistFilter(tags []string) ExactMatchTagFilter {\n\treturn newExactMatchTagFilter(tags, true)\n}\n\n// NewWhitelistFilter is a convenience method for creating a whitelist ExactMatchTagFilter\nfunc NewWhitelistFilter(tags []string) ExactMatchTagFilter {\n\treturn newExactMatchTagFilter(tags, false)\n}\n\n// FilterProcessTags implements TagFilter\nfunc (tf ExactMatchTagFilter) FilterProcessTags(_ *Span, processTags []KeyValue) []KeyValue {\n\treturn tf.filter(processTags)\n}\n\n// FilterTags implements TagFilter\nfunc (tf ExactMatchTagFilter) FilterTags(_ *Span, tags []KeyValue) []KeyValue {\n\treturn tf.filter(tags)\n}\n\n// FilterLogFields implements TagFilter\nfunc (tf ExactMatchTagFilter) FilterLogFields(_ *Span, logFields []KeyValue) []KeyValue {\n\treturn tf.filter(logFields)\n}\n\nfunc (tf ExactMatchTagFilter) filter(tags []KeyValue) []KeyValue {\n\tvar filteredTags []KeyValue\n\tfor _, t := range tags {\n\t\tif _, ok := tf.tags[t.Key]; ok == !tf.dropMatches {\n\t\t\tfilteredTags = append(filteredTags, t)\n\t\t}\n\t}\n\treturn filteredTags\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/tag_filter_exact_match_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBlacklistFilter(t *testing.T) {\n\ttt := []struct {\n\t\tinput    []string\n\t\tfilter   []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tinput:    []string{\"a\", \"b\", \"c\"},\n\t\t\tfilter:   []string{\"a\"},\n\t\t\texpected: []string{\"b\", \"c\"},\n\t\t},\n\t\t{\n\t\t\tinput:    []string{\"a\", \"b\", \"c\"},\n\t\t\tfilter:   []string{\"A\"},\n\t\t\texpected: []string{\"a\", \"b\", \"c\"},\n\t\t},\n\t}\n\n\tfor _, test := range tt {\n\t\tvar inputKVs []KeyValue\n\t\tfor _, i := range test.input {\n\t\t\tinputKVs = append(inputKVs, KeyValue{Key: i, ValueType: StringType, ValueString: \"\"})\n\t\t}\n\t\tvar expectedKVs []KeyValue\n\t\tfor _, e := range test.expected {\n\t\t\texpectedKVs = append(expectedKVs, KeyValue{Key: e, ValueType: StringType, ValueString: \"\"})\n\t\t}\n\t\tSortKVs(expectedKVs)\n\n\t\ttf := NewBlacklistFilter(test.filter)\n\t\tactualKVs := tf.filter(inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\n\t\tactualKVs = tf.FilterLogFields(nil, inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\n\t\tactualKVs = tf.FilterProcessTags(nil, inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\n\t\tactualKVs = tf.FilterTags(nil, inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\t}\n}\n\nfunc TestWhitelistFilter(t *testing.T) {\n\ttt := []struct {\n\t\tinput    []string\n\t\tfilter   []string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tinput:    []string{\"a\", \"b\", \"c\"},\n\t\t\tfilter:   []string{\"a\"},\n\t\t\texpected: []string{\"a\"},\n\t\t},\n\t\t{\n\t\t\tinput:    []string{\"a\", \"b\", \"c\"},\n\t\t\tfilter:   []string{\"A\"},\n\t\t\texpected: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tt {\n\t\tvar inputKVs []KeyValue\n\t\tfor _, i := range test.input {\n\t\t\tinputKVs = append(inputKVs, KeyValue{Key: i, ValueType: StringType, ValueString: \"\"})\n\t\t}\n\t\tvar expectedKVs []KeyValue\n\t\tfor _, e := range test.expected {\n\t\t\texpectedKVs = append(expectedKVs, KeyValue{Key: e, ValueType: StringType, ValueString: \"\"})\n\t\t}\n\t\tSortKVs(expectedKVs)\n\n\t\ttf := NewWhitelistFilter(test.filter)\n\t\tactualKVs := tf.filter(inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\n\t\tactualKVs = tf.FilterLogFields(nil, inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\n\t\tactualKVs = tf.FilterProcessTags(nil, inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\n\t\tactualKVs = tf.FilterTags(nil, inputKVs)\n\t\tSortKVs(actualKVs)\n\t\tassert.Equal(t, expectedKVs, actualKVs)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/tag_filter_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDefaultTagFilter(t *testing.T) {\n\tspan := getTestSpan()\n\texpectedTags := append(append(someDBTags, someDBTags...), someDBTags...)\n\tfilteredTags := DefaultTagFilter.FilterProcessTags(span, span.Process.Tags)\n\tfilteredTags = append(filteredTags, DefaultTagFilter.FilterTags(span, span.Tags)...)\n\tfor _, log := range span.Logs {\n\t\tfilteredTags = append(filteredTags, DefaultTagFilter.FilterLogFields(span, log.Fields)...)\n\t}\n\tcompareTags(t, expectedTags, filteredTags)\n}\n\ntype onlyStringsFilter struct{}\n\nfunc (onlyStringsFilter) filterStringTags(tags []KeyValue) []KeyValue {\n\tvar ret []KeyValue\n\tfor _, tag := range tags {\n\t\tif tag.ValueType == StringType {\n\t\t\tret = append(ret, tag)\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc (f onlyStringsFilter) FilterProcessTags(_ *Span, processTags []KeyValue) []KeyValue {\n\treturn f.filterStringTags(processTags)\n}\n\nfunc (f onlyStringsFilter) FilterTags(_ *Span, tags []KeyValue) []KeyValue {\n\treturn f.filterStringTags(tags)\n}\n\nfunc (f onlyStringsFilter) FilterLogFields(_ *Span, logFields []KeyValue) []KeyValue {\n\treturn f.filterStringTags(logFields)\n}\n\nfunc TestChainedTagFilter(t *testing.T) {\n\texpectedTags := []KeyValue{{Key: someStringTagKey, ValueType: StringType, ValueString: someStringTagValue}}\n\tfilter := NewChainedTagFilter(DefaultTagFilter, onlyStringsFilter{})\n\tfilteredTags := filter.FilterProcessTags(nil, someDBTags)\n\tcompareTags(t, expectedTags, filteredTags)\n\tfilteredTags = filter.FilterTags(nil, someDBTags)\n\tcompareTags(t, expectedTags, filteredTags)\n\tfilteredTags = filter.FilterLogFields(nil, someDBTags)\n\tcompareTags(t, expectedTags, filteredTags)\n}\n\nfunc compareTags(t *testing.T, expected, actual []KeyValue) {\n\tif !assert.Equal(t, expected, actual) {\n\t\tfor _, diff := range pretty.Diff(expected, actual) {\n\t\t\tt.Log(diff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/unique_ids.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// UniqueTraceIDs is a set of unique dbmodel TraceIDs, implemented via map.\ntype UniqueTraceIDs map[TraceID]struct{}\n\n// UniqueTraceIDsFromList Takes a list of traceIDs and returns the unique set\nfunc UniqueTraceIDsFromList(traceIDs []TraceID) UniqueTraceIDs {\n\tuniqueTraceIDs := UniqueTraceIDs{}\n\tfor _, traceID := range traceIDs {\n\t\tuniqueTraceIDs[traceID] = struct{}{}\n\t}\n\treturn uniqueTraceIDs\n}\n\n// Add adds a traceID to the existing map\nfunc (u UniqueTraceIDs) Add(traceID TraceID) {\n\tu[traceID] = struct{}{}\n}\n\n// IntersectTraceIDs takes a list of UniqueTraceIDs and intersects them.\nfunc IntersectTraceIDs(uniqueTraceIdsList []UniqueTraceIDs) UniqueTraceIDs {\n\tretMe := UniqueTraceIDs{}\n\tfor key, value := range uniqueTraceIdsList[0] {\n\t\tkeyExistsInAll := true\n\t\tfor _, otherTraceIds := range uniqueTraceIdsList[1:] {\n\t\t\tif _, ok := otherTraceIds[key]; !ok {\n\t\t\t\tkeyExistsInAll = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif keyExistsInAll {\n\t\t\tretMe[key] = value\n\t\t}\n\t}\n\treturn retMe\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/unique_ids_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nfunc TestGetIntersectedTraceIDs(t *testing.T) {\n\tfirstTraceID := TraceIDFromDomain(model.NewTraceID(1, 1))\n\tsecondTraceID := TraceIDFromDomain(model.NewTraceID(2, 2))\n\tlistOfUniqueTraceIDs := []UniqueTraceIDs{\n\t\t{\n\t\t\tfirstTraceID:  struct{}{},\n\t\t\tsecondTraceID: struct{}{},\n\t\t},\n\t\t{\n\t\t\tfirstTraceID:  struct{}{},\n\t\t\tsecondTraceID: struct{}{},\n\t\t},\n\t\t{\n\t\t\tfirstTraceID: struct{}{},\n\t\t},\n\t}\n\texpected := UniqueTraceIDs{\n\t\tfirstTraceID: struct{}{},\n\t}\n\tactual := IntersectTraceIDs(listOfUniqueTraceIDs)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestAdd(t *testing.T) {\n\tu := UniqueTraceIDs{}\n\tsomeID := TraceIDFromDomain(model.NewTraceID(1, 1))\n\tu.Add(someID)\n\tassert.Contains(t, u, someID)\n}\n\nfunc TestFromList(t *testing.T) {\n\tsomeID := TraceIDFromDomain(model.NewTraceID(1, 1))\n\ttraceIDList := []TraceID{\n\t\tsomeID,\n\t}\n\tuniqueTraceIDs := UniqueTraceIDsFromList(traceIDList)\n\tassert.Len(t, uniqueTraceIDs, 1)\n\tassert.Contains(t, uniqueTraceIDs, someID)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/unique_tags.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// GetAllUniqueTags creates a list of all unique tags from a set of filtered tags.\nfunc GetAllUniqueTags(span *Span, tagFilter TagFilter) []TagInsertion {\n\tallTags := append([]KeyValue{}, tagFilter.FilterProcessTags(span, span.Process.Tags)...)\n\tallTags = append(allTags, tagFilter.FilterTags(span, span.Tags)...)\n\tfor _, log := range span.Logs {\n\t\tallTags = append(allTags, tagFilter.FilterLogFields(span, log.Fields)...)\n\t}\n\tSortKVs(allTags)\n\tuniqueTags := make([]TagInsertion, 0, len(allTags))\n\tfor i := range allTags {\n\t\tif allTags[i].ValueType == BinaryType {\n\t\t\tcontinue // do not index binary tags\n\t\t}\n\t\tif i > 0 && allTags[i-1].Equal(&allTags[i]) {\n\t\t\tcontinue // skip identical tags\n\t\t}\n\t\tuniqueTags = append(uniqueTags, TagInsertion{\n\t\t\tServiceName: span.Process.ServiceName,\n\t\t\tTagKey:      allTags[i].Key,\n\t\t\tTagValue:    allTags[i].AsString(),\n\t\t})\n\t}\n\treturn uniqueTags\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/dbmodel/unique_tags_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetUniqueTags(t *testing.T) {\n\texpectedTags := getTestUniqueTags()\n\tuniqueTags := GetAllUniqueTags(getTestSpan(), DefaultTagFilter)\n\tif !assert.Equal(t, expectedTags, uniqueTags) {\n\t\tfor _, diff := range pretty.Diff(expectedTags, uniqueTags) {\n\t\t\tt.Log(diff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/matchers_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"strings\"\n\n\t\"github.com/stretchr/testify/mock\"\n)\n\nfunc matchOnce() any {\n\treturn matchOnceWithSideEffect(nil)\n}\n\nfunc matchOnceWithSideEffect(fn func(v []any)) any {\n\tvar matched bool\n\treturn mock.MatchedBy(func(v []any) bool {\n\t\tif matched {\n\t\t\treturn false\n\t\t}\n\t\tmatched = true\n\t\tif fn != nil {\n\t\t\tfn(v)\n\t\t}\n\t\treturn true\n\t})\n}\n\n// stringMatcher can match a string argument when it contains a specific substring q\nfunc stringMatcher(q string) any {\n\tmatchFunc := func(s string) bool {\n\t\treturn strings.Contains(s, q)\n\t}\n\treturn mock.MatchedBy(matchFunc)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewCoreSpanReader creates a new instance of CoreSpanReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewCoreSpanReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *CoreSpanReader {\n\tmock := &CoreSpanReader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// CoreSpanReader is an autogenerated mock type for the CoreSpanReader type\ntype CoreSpanReader struct {\n\tmock.Mock\n}\n\ntype CoreSpanReader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *CoreSpanReader) EXPECT() *CoreSpanReader_Expecter {\n\treturn &CoreSpanReader_Expecter{mock: &_m.Mock}\n}\n\n// FindTraceIDs provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) FindTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\tret := _mock.Called(ctx, traceQuery)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraceIDs\")\n\t}\n\n\tvar r0 []model.TraceID\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) ([]model.TraceID, error)); ok {\n\t\treturn returnFunc(ctx, traceQuery)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) []model.TraceID); ok {\n\t\tr0 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]model.TraceID)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *spanstore.TraceQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_FindTraceIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraceIDs'\ntype CoreSpanReader_FindTraceIDs_Call struct {\n\t*mock.Call\n}\n\n// FindTraceIDs is a helper method to define mock.On call\n//   - ctx context.Context\n//   - traceQuery *spanstore.TraceQueryParameters\nfunc (_e *CoreSpanReader_Expecter) FindTraceIDs(ctx interface{}, traceQuery interface{}) *CoreSpanReader_FindTraceIDs_Call {\n\treturn &CoreSpanReader_FindTraceIDs_Call{Call: _e.mock.On(\"FindTraceIDs\", ctx, traceQuery)}\n}\n\nfunc (_c *CoreSpanReader_FindTraceIDs_Call) Run(run func(ctx context.Context, traceQuery *spanstore.TraceQueryParameters)) *CoreSpanReader_FindTraceIDs_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *spanstore.TraceQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*spanstore.TraceQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraceIDs_Call) Return(traceIDs []model.TraceID, err error) *CoreSpanReader_FindTraceIDs_Call {\n\t_c.Call.Return(traceIDs, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraceIDs_Call) RunAndReturn(run func(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]model.TraceID, error)) *CoreSpanReader_FindTraceIDs_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// FindTraces provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) FindTraces(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\tret := _mock.Called(ctx, traceQuery)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraces\")\n\t}\n\n\tvar r0 []*model.Trace\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) ([]*model.Trace, error)); ok {\n\t\treturn returnFunc(ctx, traceQuery)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, *spanstore.TraceQueryParameters) []*model.Trace); ok {\n\t\tr0 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]*model.Trace)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, *spanstore.TraceQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_FindTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraces'\ntype CoreSpanReader_FindTraces_Call struct {\n\t*mock.Call\n}\n\n// FindTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - traceQuery *spanstore.TraceQueryParameters\nfunc (_e *CoreSpanReader_Expecter) FindTraces(ctx interface{}, traceQuery interface{}) *CoreSpanReader_FindTraces_Call {\n\treturn &CoreSpanReader_FindTraces_Call{Call: _e.mock.On(\"FindTraces\", ctx, traceQuery)}\n}\n\nfunc (_c *CoreSpanReader_FindTraces_Call) Run(run func(ctx context.Context, traceQuery *spanstore.TraceQueryParameters)) *CoreSpanReader_FindTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 *spanstore.TraceQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*spanstore.TraceQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraces_Call) Return(traces []*model.Trace, err error) *CoreSpanReader_FindTraces_Call {\n\t_c.Call.Return(traces, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraces_Call) RunAndReturn(run func(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]*model.Trace, error)) *CoreSpanReader_FindTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperations provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetOperations(ctx context.Context, query spanstore.OperationQueryParameters) ([]spanstore.Operation, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperations\")\n\t}\n\n\tvar r0 []spanstore.Operation\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.OperationQueryParameters) ([]spanstore.Operation, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.OperationQueryParameters) []spanstore.Operation); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]spanstore.Operation)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, spanstore.OperationQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetOperations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperations'\ntype CoreSpanReader_GetOperations_Call struct {\n\t*mock.Call\n}\n\n// GetOperations is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query spanstore.OperationQueryParameters\nfunc (_e *CoreSpanReader_Expecter) GetOperations(ctx interface{}, query interface{}) *CoreSpanReader_GetOperations_Call {\n\treturn &CoreSpanReader_GetOperations_Call{Call: _e.mock.On(\"GetOperations\", ctx, query)}\n}\n\nfunc (_c *CoreSpanReader_GetOperations_Call) Run(run func(ctx context.Context, query spanstore.OperationQueryParameters)) *CoreSpanReader_GetOperations_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 spanstore.OperationQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(spanstore.OperationQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetOperations_Call) Return(operations []spanstore.Operation, err error) *CoreSpanReader_GetOperations_Call {\n\t_c.Call.Return(operations, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetOperations_Call) RunAndReturn(run func(ctx context.Context, query spanstore.OperationQueryParameters) ([]spanstore.Operation, error)) *CoreSpanReader_GetOperations_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperationsV2 provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetOperationsV2(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperationsV2\")\n\t}\n\n\tvar r0 []tracestore.Operation\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, tracestore.OperationQueryParams) ([]tracestore.Operation, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, tracestore.OperationQueryParams) []tracestore.Operation); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]tracestore.Operation)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, tracestore.OperationQueryParams) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetOperationsV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperationsV2'\ntype CoreSpanReader_GetOperationsV2_Call struct {\n\t*mock.Call\n}\n\n// GetOperationsV2 is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query tracestore.OperationQueryParams\nfunc (_e *CoreSpanReader_Expecter) GetOperationsV2(ctx interface{}, query interface{}) *CoreSpanReader_GetOperationsV2_Call {\n\treturn &CoreSpanReader_GetOperationsV2_Call{Call: _e.mock.On(\"GetOperationsV2\", ctx, query)}\n}\n\nfunc (_c *CoreSpanReader_GetOperationsV2_Call) Run(run func(ctx context.Context, query tracestore.OperationQueryParams)) *CoreSpanReader_GetOperationsV2_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 tracestore.OperationQueryParams\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(tracestore.OperationQueryParams)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetOperationsV2_Call) Return(operations []tracestore.Operation, err error) *CoreSpanReader_GetOperationsV2_Call {\n\t_c.Call.Return(operations, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetOperationsV2_Call) RunAndReturn(run func(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error)) *CoreSpanReader_GetOperationsV2_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetServices provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetServices(ctx context.Context) ([]string, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetServices\")\n\t}\n\n\tvar r0 []string\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]string)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServices'\ntype CoreSpanReader_GetServices_Call struct {\n\t*mock.Call\n}\n\n// GetServices is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *CoreSpanReader_Expecter) GetServices(ctx interface{}) *CoreSpanReader_GetServices_Call {\n\treturn &CoreSpanReader_GetServices_Call{Call: _e.mock.On(\"GetServices\", ctx)}\n}\n\nfunc (_c *CoreSpanReader_GetServices_Call) Run(run func(ctx context.Context)) *CoreSpanReader_GetServices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetServices_Call) Return(strings []string, err error) *CoreSpanReader_GetServices_Call {\n\t_c.Call.Return(strings, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetServices_Call) RunAndReturn(run func(ctx context.Context) ([]string, error)) *CoreSpanReader_GetServices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetTrace provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTrace\")\n\t}\n\n\tvar r0 *model.Trace\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.GetTraceParameters) (*model.Trace, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, spanstore.GetTraceParameters) *model.Trace); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*model.Trace)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, spanstore.GetTraceParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetTrace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTrace'\ntype CoreSpanReader_GetTrace_Call struct {\n\t*mock.Call\n}\n\n// GetTrace is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query spanstore.GetTraceParameters\nfunc (_e *CoreSpanReader_Expecter) GetTrace(ctx interface{}, query interface{}) *CoreSpanReader_GetTrace_Call {\n\treturn &CoreSpanReader_GetTrace_Call{Call: _e.mock.On(\"GetTrace\", ctx, query)}\n}\n\nfunc (_c *CoreSpanReader_GetTrace_Call) Run(run func(ctx context.Context, query spanstore.GetTraceParameters)) *CoreSpanReader_GetTrace_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 spanstore.GetTraceParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(spanstore.GetTraceParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetTrace_Call) Return(trace *model.Trace, err error) *CoreSpanReader_GetTrace_Call {\n\t_c.Call.Return(trace, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetTrace_Call) RunAndReturn(run func(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error)) *CoreSpanReader_GetTrace_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/operation_names.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/cache\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nconst (\n\t// latestVersion of operation_names table\n\t// increase the version if your table schema changes require code change\n\tlatestVersion = schemaVersion(\"v2\")\n\n\t// previous version of operation_names table\n\t// if latest version does not work, will fail back to use previous version\n\tpreviousVersion = schemaVersion(\"v1\")\n\n\t// tableCheckStmt the query statement used to check if a table exists or not\n\ttableCheckStmt = \"SELECT * from %s limit 1\"\n)\n\ntype schemaVersion string\n\ntype tableMeta struct {\n\ttableName        string\n\tinsertStmt       string\n\tqueryByKindStmt  string\n\tqueryStmt        string\n\tcreateWriteQuery func(query cassandra.Query, service, kind, opName string) cassandra.Query\n\tgetOperations    func(\n\t\ts *OperationNamesStorage,\n\t\tquery tracestore.OperationQueryParams,\n\t) ([]tracestore.Operation, error)\n}\n\nfunc (t *tableMeta) materialize() {\n\tt.insertStmt = fmt.Sprintf(t.insertStmt, t.tableName)\n\tt.queryByKindStmt = fmt.Sprintf(t.queryByKindStmt, t.tableName)\n\tt.queryStmt = fmt.Sprintf(t.queryStmt, t.tableName)\n}\n\nvar schemas = map[schemaVersion]tableMeta{\n\tpreviousVersion: {\n\t\ttableName:       \"operation_names\",\n\t\tinsertStmt:      \"INSERT INTO %s(service_name, operation_name) VALUES (?, ?)\",\n\t\tqueryByKindStmt: \"SELECT operation_name FROM %s WHERE service_name = ?\",\n\t\tqueryStmt:       \"SELECT operation_name FROM %s WHERE service_name = ?\",\n\t\tgetOperations:   getOperationsV1,\n\t\tcreateWriteQuery: func(query cassandra.Query, service, _ /* kind*/, opName string) cassandra.Query {\n\t\t\treturn query.Bind(service, opName)\n\t\t},\n\t},\n\tlatestVersion: {\n\t\ttableName:       \"operation_names_v2\",\n\t\tinsertStmt:      \"INSERT INTO %s(service_name, span_kind, operation_name) VALUES (?, ?, ?)\",\n\t\tqueryByKindStmt: \"SELECT span_kind, operation_name FROM %s WHERE service_name = ? AND span_kind = ?\",\n\t\tqueryStmt:       \"SELECT span_kind, operation_name FROM %s WHERE service_name = ?\",\n\t\tgetOperations:   getOperationsV2,\n\t\tcreateWriteQuery: func(query cassandra.Query, service, kind, opName string) cassandra.Query {\n\t\t\treturn query.Bind(service, kind, opName)\n\t\t},\n\t},\n}\n\n// OperationNamesStorage stores known operation names by service.\ntype OperationNamesStorage struct {\n\t// CQL statements are public so that Cassandra2 storage can override them\n\tschemaVersion  schemaVersion\n\ttable          tableMeta\n\tsession        cassandra.Session\n\twriteCacheTTL  time.Duration\n\tmetrics        *casmetrics.Table\n\toperationNames cache.Cache\n\tlogger         *zap.Logger\n}\n\n// NewOperationNamesStorage returns a new OperationNamesStorage\nfunc NewOperationNamesStorage(\n\tsession cassandra.Session,\n\twriteCacheTTL time.Duration,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n) (*OperationNamesStorage, error) {\n\tschemaVersion := latestVersion\n\tif !tableExist(session, schemas[schemaVersion].tableName) {\n\t\tif !tableExist(session, schemas[previousVersion].tableName) {\n\t\t\treturn nil, fmt.Errorf(\"neither table %s nor %s exist\",\n\t\t\t\tschemas[schemaVersion].tableName, schemas[previousVersion].tableName)\n\t\t}\n\t\tschemaVersion = previousVersion\n\t}\n\ttable := schemas[schemaVersion]\n\ttable.materialize()\n\n\treturn &OperationNamesStorage{\n\t\tsession:       session,\n\t\tschemaVersion: schemaVersion,\n\t\ttable:         table,\n\t\tmetrics:       casmetrics.NewTable(metricsFactory, schemas[schemaVersion].tableName),\n\t\twriteCacheTTL: writeCacheTTL,\n\t\tlogger:        logger,\n\t\toperationNames: cache.NewLRUWithOptions(\n\t\t\t100000,\n\t\t\t&cache.Options{\n\t\t\t\tTTL:             writeCacheTTL,\n\t\t\t\tInitialCapacity: 10000,\n\t\t\t}),\n\t}, nil\n}\n\n// Write saves Operation and Service name tuples\nfunc (s *OperationNamesStorage) Write(operation dbmodel.Operation) error {\n\tkey := fmt.Sprintf(\"%s|%s|%s\",\n\t\toperation.ServiceName,\n\t\toperation.SpanKind,\n\t\toperation.OperationName,\n\t)\n\tif inCache := checkWriteCache(key, s.operationNames, s.writeCacheTTL); !inCache {\n\t\tq := s.table.createWriteQuery(\n\t\t\ts.session.Query(s.table.insertStmt),\n\t\t\toperation.ServiceName,\n\t\t\toperation.SpanKind,\n\t\t\toperation.OperationName,\n\t\t)\n\t\terr := s.metrics.Exec(q, s.logger)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetOperations returns all operations for a specific service traced by Jaeger\nfunc (s *OperationNamesStorage) GetOperations(\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\treturn s.table.getOperations(s, query)\n}\n\nfunc tableExist(session cassandra.Session, tableName string) bool {\n\tquery := session.Query(fmt.Sprintf(tableCheckStmt, tableName))\n\terr := query.Exec()\n\treturn err == nil\n}\n\nfunc getOperationsV1(\n\ts *OperationNamesStorage,\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\titer := s.session.Query(s.table.queryStmt, query.ServiceName).Iter()\n\n\tvar operation string\n\tvar operations []tracestore.Operation\n\tfor iter.Scan(&operation) {\n\t\toperations = append(operations, tracestore.Operation{\n\t\t\tName: operation,\n\t\t})\n\t}\n\tif err := iter.Close(); err != nil {\n\t\terr = fmt.Errorf(\"error reading operation_names from storage: %w\", err)\n\t\treturn nil, err\n\t}\n\n\treturn operations, nil\n}\n\nfunc getOperationsV2(\n\ts *OperationNamesStorage,\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\tvar casQuery cassandra.Query\n\tif query.SpanKind == \"\" {\n\t\t// Get operations for all spanKind\n\t\tcasQuery = s.session.Query(s.table.queryStmt, query.ServiceName)\n\t} else {\n\t\t// Get operations for given spanKind\n\t\tcasQuery = s.session.Query(s.table.queryByKindStmt, query.ServiceName, query.SpanKind)\n\t}\n\titer := casQuery.Iter()\n\n\tvar operationName string\n\tvar spanKind string\n\tvar operations []tracestore.Operation\n\tfor iter.Scan(&spanKind, &operationName) {\n\t\toperations = append(operations, tracestore.Operation{\n\t\t\tName:     operationName,\n\t\t\tSpanKind: spanKind,\n\t\t})\n\t}\n\tif err := iter.Close(); err != nil {\n\t\terr = fmt.Errorf(\"error reading %s from storage: %w\", s.table.tableName, err)\n\t\treturn nil, err\n\t}\n\treturn operations, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/operation_names_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype operationNameStorageTest struct {\n\tsession        *mocks.Session\n\twriteCacheTTL  time.Duration\n\tmetricsFactory *metricstest.Factory\n\tlogger         *zap.Logger\n\tlogBuffer      *testutils.Buffer\n\tstorage        *OperationNamesStorage\n}\n\nfunc withOperationNamesStorage(t *testing.T,\n\twriteCacheTTL time.Duration,\n\tschemaVersion schemaVersion,\n\tfn func(s *operationNameStorageTest),\n) {\n\tsession := &mocks.Session{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\tlatestTableCheckquery := &mocks.Query{}\n\tsession.On(\"Query\",\n\t\tfmt.Sprintf(tableCheckStmt, schemas[latestVersion].tableName), mock.Anything).Return(latestTableCheckquery)\n\tif schemaVersion == latestVersion {\n\t\tlatestTableCheckquery.On(\"Exec\").Return(nil)\n\t} else {\n\t\tpreviousTableCheckquery := &mocks.Query{}\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[previousVersion].tableName), mock.Anything).Return(previousTableCheckquery)\n\t\tlatestTableCheckquery.On(\"Exec\").Return(errors.New(\"table not found\"))\n\t\tpreviousTableCheckquery.On(\"Exec\").Return(nil)\n\t}\n\n\tstorage, err := NewOperationNamesStorage(session, writeCacheTTL, metricsFactory, logger)\n\trequire.NoError(t, err)\n\n\ts := &operationNameStorageTest{\n\t\tsession:        session,\n\t\twriteCacheTTL:  writeCacheTTL,\n\t\tmetricsFactory: metricsFactory,\n\t\tlogger:         logger,\n\t\tlogBuffer:      logBuffer,\n\t\tstorage:        storage,\n\t}\n\tfn(s)\n}\n\nfunc TestNewOperationNamesStorage(t *testing.T) {\n\tt.Run(\"test operation names storage creation with old schema\", func(t *testing.T) {\n\t\twithOperationNamesStorage(t, 0, previousVersion, func(s *operationNameStorageTest) {\n\t\t\tassert.NotNil(t, s.storage)\n\t\t})\n\t})\n\n\tt.Run(\"test operation names storage creation with new schema\", func(t *testing.T) {\n\t\twithOperationNamesStorage(t, 0, latestVersion, func(s *operationNameStorageTest) {\n\t\t\tassert.NotNil(t, s.storage)\n\t\t})\n\t})\n\n\tt.Run(\"test operation names storage creation error\", func(t *testing.T) {\n\t\tsession := &mocks.Session{}\n\t\tlogger, _ := testutils.NewLogger()\n\t\tmetricsFactory := metricstest.NewFactory(0)\n\t\tquery := &mocks.Query{}\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[latestVersion].tableName),\n\t\t\tmock.Anything).Return(query)\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[previousVersion].tableName),\n\t\t\tmock.Anything).Return(query)\n\t\tquery.On(\"Exec\").Return(errors.New(\"table does not exist\"))\n\n\t\t_, err := NewOperationNamesStorage(session, 0, metricsFactory, logger)\n\n\t\trequire.EqualError(t, err, \"neither table operation_names_v2 nor operation_names exist\")\n\t})\n}\n\nfunc TestOperationNamesStorageWrite(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tname          string\n\t\tttl           time.Duration\n\t\tschemaVersion schemaVersion\n\t}{\n\t\t{name: \"test old schema with 0 ttl\", ttl: 0, schemaVersion: previousVersion},\n\t\t{name: \"test old schema with 1min ttl\", ttl: time.Minute, schemaVersion: previousVersion},\n\t\t{name: \"test new schema with 0 ttl\", ttl: 0, schemaVersion: latestVersion},\n\t\t{name: \"test new schema with 1min ttl\", ttl: time.Minute, schemaVersion: latestVersion},\n\t} {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\twithOperationNamesStorage(t, test.ttl, test.schemaVersion, func(s *operationNameStorageTest) {\n\t\t\t\texecError := errors.New(\"exec error\")\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery1 := &mocks.Query{}\n\t\t\t\tquery2 := &mocks.Query{}\n\n\t\t\t\tif test.schemaVersion == previousVersion {\n\t\t\t\t\tquery.On(\"Bind\", []any{\"service-a\", \"Operation-b\"}).Return(query1)\n\t\t\t\t\tquery.On(\"Bind\", []any{\"service-c\", \"operation-d\"}).Return(query2)\n\t\t\t\t} else {\n\t\t\t\t\tquery.On(\"Bind\", []any{\"service-a\", \"\", \"Operation-b\"}).Return(query1)\n\t\t\t\t\tquery.On(\"Bind\", []any{\"service-c\", \"\", \"operation-d\"}).Return(query2)\n\t\t\t\t}\n\n\t\t\t\tquery1.On(\"Exec\").Return(nil)\n\t\t\t\tquery2.On(\"Exec\").Return(execError)\n\t\t\t\tquery2.On(\"String\").Return(\"select from \" + schemas[test.schemaVersion].tableName)\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\n\t\t\t\terr := s.storage.Write(dbmodel.Operation{\n\t\t\t\t\tServiceName:   \"service-a\",\n\t\t\t\t\tOperationName: \"Operation-b\",\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\terr = s.storage.Write(dbmodel.Operation{\n\t\t\t\t\tServiceName:   \"service-c\",\n\t\t\t\t\tOperationName: \"operation-d\",\n\t\t\t\t})\n\t\t\t\trequire.EqualError(t, err,\n\t\t\t\t\t\"failed to Exec query 'select from \"+\n\t\t\t\t\t\tschemas[test.schemaVersion].tableName+\n\t\t\t\t\t\t\"': exec error\")\n\t\t\t\tassert.Equal(t, map[string]string{\n\t\t\t\t\t\"level\": \"error\",\n\t\t\t\t\t\"msg\":   \"Failed to exec query\",\n\t\t\t\t\t\"query\": \"select from \" + schemas[test.schemaVersion].tableName,\n\t\t\t\t\t\"error\": \"exec error\",\n\t\t\t\t}, s.logBuffer.JSONLine(0))\n\n\t\t\t\tcounts, _ := s.metricsFactory.Snapshot()\n\t\t\t\tassert.Equal(t, map[string]int64{\n\t\t\t\t\t\"attempts|table=\" + schemas[test.schemaVersion].tableName: 2,\n\t\t\t\t\t\"inserts|table=\" + schemas[test.schemaVersion].tableName:  1,\n\t\t\t\t\t\"errors|table=\" + schemas[test.schemaVersion].tableName:   1,\n\t\t\t\t}, counts, \"after first two writes\")\n\n\t\t\t\t// write again\n\t\t\t\terr = s.storage.Write(dbmodel.Operation{\n\t\t\t\t\tServiceName:   \"service-a\",\n\t\t\t\t\tOperationName: \"Operation-b\",\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tcounts2, _ := s.metricsFactory.Snapshot()\n\t\t\t\texpCounts := counts\n\t\t\t\tif test.ttl == 0 {\n\t\t\t\t\t// without write cache, the second write must succeed\n\t\t\t\t\texpCounts[\"attempts|table=\"+schemas[test.schemaVersion].tableName]++\n\t\t\t\t\texpCounts[\"inserts|table=\"+schemas[test.schemaVersion].tableName]++\n\t\t\t\t}\n\t\t\t\tassert.Equal(t, expCounts, counts2)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestOperationNamesStorageGetServices(t *testing.T) {\n\tscanError := errors.New(\"scan error\")\n\tfor _, test := range []struct {\n\t\tname          string\n\t\tschemaVersion schemaVersion\n\t\texpErr        error\n\t\texpRes        []tracestore.Operation\n\t}{\n\t\t{\n\t\t\tname:          \"test old schema without error\",\n\t\t\tschemaVersion: previousVersion,\n\t\t\texpRes:        []tracestore.Operation{{Name: \"foo\"}},\n\t\t},\n\t\t{\n\t\t\tname:          \"test new schema without error\",\n\t\t\tschemaVersion: latestVersion,\n\t\t\texpRes:        []tracestore.Operation{{SpanKind: \"foo\", Name: \"bar\"}},\n\t\t},\n\t\t{name: \"test old schema with scan error\", schemaVersion: previousVersion, expErr: scanError},\n\t\t{name: \"test new schema with scan error\", schemaVersion: latestVersion, expErr: scanError},\n\t} {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\twithOperationNamesStorage(t, 0, test.schemaVersion, func(s *operationNameStorageTest) {\n\t\t\t\tassignPtr := func(vals ...string) any {\n\t\t\t\t\treturn mock.MatchedBy(func(args []any) bool {\n\t\t\t\t\t\tif len(args) != len(vals) {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor i, arg := range args {\n\t\t\t\t\t\t\tptr, ok := arg.(*string)\n\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t*ptr = vals[i]\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\titer := &mocks.Iterator{}\n\t\t\t\tif test.schemaVersion == previousVersion {\n\t\t\t\t\titer.On(\"Scan\", assignPtr(\"foo\")).Return(true).Once()\n\t\t\t\t} else {\n\t\t\t\t\titer.On(\"Scan\", assignPtr(\"foo\", \"bar\")).Return(true).Once()\n\t\t\t\t}\n\t\t\t\titer.On(\"Scan\", mock.Anything).Return(false) // false to stop the loop\n\t\t\t\titer.On(\"Close\").Return(test.expErr)\n\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"Iter\").Return(iter)\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\t\t\t\tservices, err := s.storage.GetOperations(tracestore.OperationQueryParams{\n\t\t\t\t\tServiceName: \"service-a\",\n\t\t\t\t})\n\t\t\t\tif test.expErr == nil {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.Equal(t, test.expRes, services)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(\n\t\t\t\t\t\tt,\n\t\t\t\t\t\terr,\n\t\t\t\t\t\tfmt.Sprintf(\"error reading %s from storage: %s\",\n\t\t\t\t\t\t\tschemas[test.schemaVersion].tableName,\n\t\t\t\t\t\t\ttest.expErr.Error()),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/reader.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nconst (\n\tbucketRange        = `(0,1,2,3,4,5,6,7,8,9)`\n\tquerySpanByTraceID = `\n\t\tSELECT trace_id, span_id, parent_id, operation_name, flags, start_time, duration, tags, logs, refs, process\n\t\tFROM traces\n\t\tWHERE trace_id = ?`\n\tqueryByTag = `\n\t\tSELECT trace_id\n\t\tFROM tag_index\n\t\tWHERE service_name = ? AND tag_key = ? AND tag_value = ? and start_time > ? and start_time < ?\n\t\tORDER BY start_time DESC\n\t\tLIMIT ?`\n\tqueryByServiceName = `\n\t\tSELECT trace_id\n\t\tFROM service_name_index\n\t\tWHERE bucket IN ` + bucketRange + ` AND service_name = ? AND start_time > ? AND start_time < ?\n\t\tORDER BY start_time DESC\n\t\tLIMIT ?`\n\tqueryByServiceAndOperationName = `\n\t\tSELECT trace_id\n\t\tFROM service_operation_index\n\t\tWHERE service_name = ? AND operation_name = ? AND start_time > ? AND start_time < ?\n\t\tORDER BY start_time DESC\n\t\tLIMIT ?`\n\tqueryByDuration = `\n\t\tSELECT trace_id\n\t\tFROM duration_index\n\t\tWHERE bucket = ? AND service_name = ? AND operation_name = ? AND duration > ? AND duration < ?\n\t\tLIMIT ?`\n\n\tdefaultNumTraces = 100\n\t// limitMultiple exists because many spans that are returned from indices can have the same trace, limitMultiple increases\n\t// the number of responses from the index, so we can respect the user's limit value they provided.\n\tlimitMultiple = 3\n)\n\nvar (\n\t// ErrServiceNameNotSet occurs when attempting to query with an empty service name\n\tErrServiceNameNotSet = errors.New(\"service Name must be set\")\n\n\t// ErrStartTimeMinGreaterThanMax occurs when start time min is above start time max\n\tErrStartTimeMinGreaterThanMax = errors.New(\"start Time Minimum is above Maximum\")\n\n\t// ErrDurationMinGreaterThanMax occurs when duration min is above duration max\n\tErrDurationMinGreaterThanMax = errors.New(\"duration Minimum is above Maximum\")\n\n\t// ErrMalformedRequestObject occurs when a request object is nil\n\tErrMalformedRequestObject = errors.New(\"malformed request object\")\n\n\t// ErrDurationAndTagQueryNotSupported occurs when duration and tags are both set\n\tErrDurationAndTagQueryNotSupported = errors.New(\"cannot query for duration and tags simultaneously\")\n\n\t// ErrStartAndEndTimeNotSet occurs when start time and end time are not set\n\tErrStartAndEndTimeNotSet = errors.New(\"start and End Time must be set\")\n)\n\ntype serviceNamesReader func() ([]string, error)\n\ntype operationNamesReader func(query tracestore.OperationQueryParams) ([]tracestore.Operation, error)\n\ntype spanReaderMetrics struct {\n\treadTraces                 *casmetrics.Table\n\tqueryTrace                 *casmetrics.Table\n\tqueryTagIndex              *casmetrics.Table\n\tqueryDurationIndex         *casmetrics.Table\n\tqueryServiceOperationIndex *casmetrics.Table\n\tqueryServiceNameIndex      *casmetrics.Table\n}\n\ntype CoreSpanReader interface {\n\tGetServices(ctx context.Context) ([]string, error)\n\tGetOperations(ctx context.Context, query spanstore.OperationQueryParameters) ([]spanstore.Operation, error)\n\tGetOperationsV2(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error)\n\tGetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error)\n\tFindTraces(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]*model.Trace, error)\n\tFindTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]model.TraceID, error)\n}\n\n// SpanReader can query for and load traces from Cassandra.\ntype SpanReader struct {\n\tsession              cassandra.Session\n\tserviceNamesReader   serviceNamesReader\n\toperationNamesReader operationNamesReader\n\tmetrics              spanReaderMetrics\n\tlogger               *zap.Logger\n\ttracer               trace.Tracer\n}\n\n// NewSpanReader returns a new SpanReader.\nfunc NewSpanReader(\n\tsession cassandra.Session,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n\ttracer trace.Tracer,\n) (*SpanReader, error) {\n\treadFactory := metricsFactory.Namespace(metrics.NSOptions{Name: \"read\", Tags: nil})\n\tserviceNamesStorage := NewServiceNamesStorage(session, 0, metricsFactory, logger)\n\toperationNamesStorage, err := NewOperationNamesStorage(session, 0, metricsFactory, logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &SpanReader{\n\t\tsession:              session,\n\t\tserviceNamesReader:   serviceNamesStorage.GetServices,\n\t\toperationNamesReader: operationNamesStorage.GetOperations,\n\t\tmetrics: spanReaderMetrics{\n\t\t\treadTraces:                 casmetrics.NewTable(readFactory, \"read_traces\"),\n\t\t\tqueryTrace:                 casmetrics.NewTable(readFactory, \"query_traces\"),\n\t\t\tqueryTagIndex:              casmetrics.NewTable(readFactory, \"tag_index\"),\n\t\t\tqueryDurationIndex:         casmetrics.NewTable(readFactory, \"duration_index\"),\n\t\t\tqueryServiceOperationIndex: casmetrics.NewTable(readFactory, \"service_operation_index\"),\n\t\t\tqueryServiceNameIndex:      casmetrics.NewTable(readFactory, \"service_name_index\"),\n\t\t},\n\t\tlogger: logger,\n\t\ttracer: tracer,\n\t}, nil\n}\n\n// GetServices returns all services traced by Jaeger\nfunc (s *SpanReader) GetServices(context.Context) ([]string, error) {\n\treturn s.serviceNamesReader()\n}\n\n// GetOperations returns all operations for a specific service traced by Jaeger\nfunc (*SpanReader) GetOperations(\n\t_ context.Context,\n\t_ spanstore.OperationQueryParameters,\n) ([]spanstore.Operation, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\nfunc (s *SpanReader) GetOperationsV2(\n\t_ context.Context,\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\treturn s.operationNamesReader(query)\n}\n\nfunc (s *SpanReader) readTrace(ctx context.Context, traceID dbmodel.TraceID) (*model.Trace, error) {\n\tctx, span := s.startSpanForQuery(ctx, \"readTrace\", querySpanByTraceID)\n\tdefer span.End()\n\tspan.SetAttributes(attribute.Key(\"trace_id\").String(traceID.String()))\n\n\ttrc, err := s.readTraceInSpan(ctx, traceID)\n\tlogErrorToSpan(span, err)\n\treturn trc, err\n}\n\nfunc (s *SpanReader) readTraceInSpan(_ context.Context, traceID dbmodel.TraceID) (*model.Trace, error) {\n\tstart := time.Now()\n\tq := s.session.Query(querySpanByTraceID, traceID)\n\ti := q.Iter()\n\tvar traceIDFromSpan dbmodel.TraceID\n\tvar startTime, spanID, duration, parentID int64\n\tvar flags int32\n\tvar operationName string\n\tvar dbProcess dbmodel.Process\n\tvar refs []dbmodel.SpanRef\n\tvar tags []dbmodel.KeyValue\n\tvar logs []dbmodel.Log\n\tretMe := &model.Trace{}\n\tfor i.Scan(&traceIDFromSpan, &spanID, &parentID, &operationName, &flags, &startTime, &duration, &tags, &logs, &refs, &dbProcess) {\n\t\tdbSpan := dbmodel.Span{\n\t\t\tTraceID:       traceIDFromSpan,\n\t\t\tSpanID:        spanID,\n\t\t\tParentID:      parentID,\n\t\t\tOperationName: operationName,\n\t\t\tFlags:         flags,\n\t\t\tStartTime:     startTime,\n\t\t\tDuration:      duration,\n\t\t\tTags:          tags,\n\t\t\tLogs:          logs,\n\t\t\tRefs:          refs,\n\t\t\tProcess:       dbProcess,\n\t\t\tServiceName:   dbProcess.ServiceName,\n\t\t}\n\t\tspan, err := dbmodel.ToDomain(&dbSpan)\n\t\tif err != nil {\n\t\t\ts.metrics.readTraces.Emit(err, time.Since(start))\n\t\t\treturn nil, err\n\t\t}\n\t\tretMe.Spans = append(retMe.Spans, span)\n\t}\n\n\terr := i.Close()\n\ts.metrics.readTraces.Emit(err, time.Since(start))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading traces from storage: %w\", err)\n\t}\n\tif len(retMe.Spans) == 0 {\n\t\treturn nil, spanstore.ErrTraceNotFound\n\t}\n\treturn retMe, nil\n}\n\n// GetTrace takes a traceID and returns a Trace associated with that traceID\nfunc (s *SpanReader) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\treturn s.readTrace(ctx, dbmodel.TraceIDFromDomain(query.TraceID))\n}\n\nfunc validateQuery(p *spanstore.TraceQueryParameters) error {\n\tif p == nil {\n\t\treturn ErrMalformedRequestObject\n\t}\n\tif p.ServiceName == \"\" && len(p.Tags) > 0 {\n\t\treturn ErrServiceNameNotSet\n\t}\n\tif p.StartTimeMin.IsZero() || p.StartTimeMax.IsZero() {\n\t\treturn ErrStartAndEndTimeNotSet\n\t}\n\tif !p.StartTimeMin.IsZero() && !p.StartTimeMax.IsZero() && p.StartTimeMax.Before(p.StartTimeMin) {\n\t\treturn ErrStartTimeMinGreaterThanMax\n\t}\n\tif p.DurationMin != 0 && p.DurationMax != 0 && p.DurationMin > p.DurationMax {\n\t\treturn ErrDurationMinGreaterThanMax\n\t}\n\tif (p.DurationMin != 0 || p.DurationMax != 0) && len(p.Tags) > 0 {\n\t\treturn ErrDurationAndTagQueryNotSupported\n\t}\n\treturn nil\n}\n\n// FindTraces retrieves traces that match the traceQuery\nfunc (s *SpanReader) FindTraces(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\tuniqueTraceIDs, err := s.FindTraceIDs(ctx, traceQuery)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar retMe []*model.Trace\n\tfor _, traceID := range uniqueTraceIDs {\n\t\tjTrace, err := s.GetTrace(ctx, spanstore.GetTraceParameters{TraceID: traceID})\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Failure to read trace\", zap.String(\"trace_id\", traceID.String()), zap.Error(err))\n\t\t\tcontinue\n\t\t}\n\t\tretMe = append(retMe, jTrace)\n\t}\n\treturn retMe, nil\n}\n\n// FindTraceIDs retrieve traceIDs that match the traceQuery\nfunc (s *SpanReader) FindTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\tif err := validateQuery(traceQuery); err != nil {\n\t\treturn nil, err\n\t}\n\tif traceQuery.NumTraces == 0 {\n\t\ttraceQuery.NumTraces = defaultNumTraces\n\t}\n\n\tdbTraceIDs, err := s.findTraceIDsFromQuery(ctx, traceQuery)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar traceIDs []model.TraceID\n\tfor t := range dbTraceIDs {\n\t\tif len(traceIDs) >= traceQuery.NumTraces {\n\t\t\tbreak\n\t\t}\n\t\ttraceIDs = append(traceIDs, t.ToDomain())\n\t}\n\treturn traceIDs, nil\n}\n\nfunc (s *SpanReader) findTraceIDsFromQuery(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {\n\t// See docs/adr/cassandra-find-traces-duration.md for rationale: duration queries use the duration_index\n\t// and are handled as a separate path. Other query parameters (like tags) are ignored when duration is specified.\n\tif traceQuery.DurationMin != 0 || traceQuery.DurationMax != 0 {\n\t\treturn s.queryByDuration(ctx, traceQuery)\n\t}\n\n\tif traceQuery.OperationName != \"\" {\n\t\ttraceIds, err := s.queryByServiceNameAndOperation(ctx, traceQuery)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(traceQuery.Tags) > 0 {\n\t\t\ttagTraceIds, err := s.queryByTagsAndLogs(ctx, traceQuery)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn dbmodel.IntersectTraceIDs([]dbmodel.UniqueTraceIDs{\n\t\t\t\ttraceIds,\n\t\t\t\ttagTraceIds,\n\t\t\t}), nil\n\t\t}\n\t\treturn traceIds, nil\n\t}\n\tif len(traceQuery.Tags) > 0 {\n\t\treturn s.queryByTagsAndLogs(ctx, traceQuery)\n\t}\n\treturn s.queryByService(ctx, traceQuery)\n}\n\nfunc (s *SpanReader) queryByTagsAndLogs(ctx context.Context, tq *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {\n\tctx, span := s.startSpanForQuery(ctx, \"queryByTagsAndLogs\", queryByTag)\n\tdefer span.End()\n\n\tresults := make([]dbmodel.UniqueTraceIDs, 0, len(tq.Tags))\n\tfor k, v := range tq.Tags {\n\t\t_, childSpan := s.tracer.Start(ctx, \"queryByTag\")\n\t\tchildSpan.SetAttributes(\n\t\t\tattribute.Key(\"tag.key\").String(k),\n\t\t\tattribute.Key(\"tag.value\").String(v),\n\t\t)\n\t\tquery := s.session.Query(\n\t\t\tqueryByTag,\n\t\t\ttq.ServiceName,\n\t\t\tk,\n\t\t\tv,\n\t\t\tmodel.TimeAsEpochMicroseconds(tq.StartTimeMin),\n\t\t\tmodel.TimeAsEpochMicroseconds(tq.StartTimeMax),\n\t\t\ttq.NumTraces*limitMultiple,\n\t\t).PageSize(0)\n\t\tt, err := s.executeQuery(childSpan, query, s.metrics.queryTagIndex)\n\t\tchildSpan.End()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresults = append(results, t)\n\t}\n\treturn dbmodel.IntersectTraceIDs(results), nil\n}\n\nfunc (s *SpanReader) queryByDuration(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {\n\tctx, span := s.startSpanForQuery(ctx, \"queryByDuration\", queryByDuration)\n\tdefer span.End()\n\n\tresults := dbmodel.UniqueTraceIDs{}\n\n\tminDurationMicros := traceQuery.DurationMin.Nanoseconds() / int64(time.Microsecond/time.Nanosecond)\n\tmaxDurationMicros := (time.Hour * 24).Nanoseconds() / int64(time.Microsecond/time.Nanosecond)\n\tif traceQuery.DurationMax != 0 {\n\t\tmaxDurationMicros = traceQuery.DurationMax.Nanoseconds() / int64(time.Microsecond/time.Nanosecond)\n\t}\n\n\t// See writer.go:indexByDuration  for how this is indexed\n\t// This is indexed in hours since epoch\n\tstartTimeByHour := traceQuery.StartTimeMin.Round(durationBucketSize)\n\tendTimeByHour := traceQuery.StartTimeMax.Round(durationBucketSize)\n\n\tfor timeBucket := endTimeByHour; timeBucket.After(startTimeByHour) || timeBucket.Equal(startTimeByHour); timeBucket = timeBucket.Add(-1 * durationBucketSize) {\n\t\t_, childSpan := s.tracer.Start(ctx, \"queryForTimeBucket\")\n\t\tchildSpan.SetAttributes(attribute.Key(\"timeBucket\").String(timeBucket.String()))\n\t\tquery := s.session.Query(\n\t\t\tqueryByDuration,\n\t\t\ttimeBucket,\n\t\t\ttraceQuery.ServiceName,\n\t\t\ttraceQuery.OperationName,\n\t\t\tminDurationMicros,\n\t\t\tmaxDurationMicros,\n\t\t\ttraceQuery.NumTraces*limitMultiple)\n\t\tt, err := s.executeQuery(childSpan, query, s.metrics.queryDurationIndex)\n\t\tchildSpan.End()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor traceID := range t {\n\t\t\tresults.Add(traceID)\n\t\t\tif len(results) == traceQuery.NumTraces {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s *SpanReader) queryByServiceNameAndOperation(ctx context.Context, tq *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {\n\t_, span := s.startSpanForQuery(ctx, \"queryByServiceNameAndOperation\", queryByServiceAndOperationName)\n\tdefer span.End()\n\tquery := s.session.Query(\n\t\tqueryByServiceAndOperationName,\n\t\ttq.ServiceName,\n\t\ttq.OperationName,\n\t\tmodel.TimeAsEpochMicroseconds(tq.StartTimeMin),\n\t\tmodel.TimeAsEpochMicroseconds(tq.StartTimeMax),\n\t\ttq.NumTraces*limitMultiple,\n\t).PageSize(0)\n\treturn s.executeQuery(span, query, s.metrics.queryServiceOperationIndex)\n}\n\nfunc (s *SpanReader) queryByService(ctx context.Context, tq *spanstore.TraceQueryParameters) (dbmodel.UniqueTraceIDs, error) {\n\t_, span := s.startSpanForQuery(ctx, \"queryByService\", queryByServiceAndOperationName)\n\tdefer span.End()\n\tquery := s.session.Query(\n\t\tqueryByServiceName,\n\t\ttq.ServiceName,\n\t\tmodel.TimeAsEpochMicroseconds(tq.StartTimeMin),\n\t\tmodel.TimeAsEpochMicroseconds(tq.StartTimeMax),\n\t\ttq.NumTraces*limitMultiple,\n\t).PageSize(0)\n\treturn s.executeQuery(span, query, s.metrics.queryServiceNameIndex)\n}\n\nfunc (s *SpanReader) executeQuery(span trace.Span, query cassandra.Query, tableMetrics *casmetrics.Table) (dbmodel.UniqueTraceIDs, error) {\n\tstart := time.Now()\n\ti := query.Iter()\n\tretMe := dbmodel.UniqueTraceIDs{}\n\tvar traceID dbmodel.TraceID\n\tfor i.Scan(&traceID) {\n\t\tretMe.Add(traceID)\n\t}\n\terr := i.Close()\n\ttableMetrics.Emit(err, time.Since(start))\n\tif err != nil {\n\t\tlogErrorToSpan(span, err)\n\t\ts.logger.Error(\"Failed to exec query\", zap.Error(err), zap.String(\"query\", query.String()))\n\t\treturn nil, err\n\t}\n\treturn retMe, nil\n}\n\nfunc (s *SpanReader) startSpanForQuery(ctx context.Context, name, query string) (context.Context, trace.Span) {\n\tctx, span := s.tracer.Start(ctx, name)\n\tspan.SetAttributes(\n\t\tattribute.Key(otelsemconv.DBQueryTextKey).String(query),\n\t\tattribute.Key(otelsemconv.DBSystemKey).String(\"cassandra\"),\n\t\tattribute.Key(\"component\").String(\"gocql\"),\n\t)\n\treturn ctx, span\n}\n\nfunc logErrorToSpan(span trace.Span, err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\tspan.RecordError(err)\n\tspan.SetStatus(codes.Error, err.Error())\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/reader_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/sdk/trace/tracetest\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype spanReaderTest struct {\n\tsession     *mocks.Session\n\tlogger      *zap.Logger\n\tlogBuffer   *testutils.Buffer\n\ttraceBuffer *tracetest.InMemoryExporter\n\treader      *SpanReader\n}\n\nfunc tracerProvider(t *testing.T) (trace.TracerProvider, *tracetest.InMemoryExporter, func()) {\n\texporter := tracetest.NewInMemoryExporter()\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()),\n\t\tsdktrace.WithSyncer(exporter),\n\t)\n\tcloser := func() {\n\t\trequire.NoError(t, tp.Shutdown(context.Background()))\n\t}\n\treturn tp, exporter, closer\n}\n\nfunc withSpanReader(t *testing.T, fn func(r *spanReaderTest)) {\n\tsession := &mocks.Session{}\n\tquery := &mocks.Query{}\n\tsession.On(\"Query\",\n\t\tfmt.Sprintf(tableCheckStmt, schemas[latestVersion].tableName),\n\t\tmock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(nil)\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\treader, err := NewSpanReader(session, metricsFactory, logger, tracer.Tracer(\"test\"))\n\trequire.NoError(t, err)\n\tr := &spanReaderTest{\n\t\tsession:     session,\n\t\tlogger:      logger,\n\t\tlogBuffer:   logBuffer,\n\t\ttraceBuffer: exp,\n\t\treader:      reader,\n\t}\n\tfn(r)\n}\n\nfunc TestNewSpanReader(t *testing.T) {\n\tt.Run(\"test span reader creation\", func(t *testing.T) {\n\t\twithSpanReader(t, func(r *spanReaderTest) {\n\t\t\tassert.NotNil(t, r.reader)\n\t\t})\n\t})\n\n\tt.Run(\"test span reader creation error\", func(t *testing.T) {\n\t\tsession := &mocks.Session{}\n\t\tquery := &mocks.Query{}\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[latestVersion].tableName),\n\t\t\tmock.Anything).Return(query)\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[previousVersion].tableName),\n\t\t\tmock.Anything).Return(query)\n\t\tquery.On(\"Exec\").Return(errors.New(\"table does not exist\"))\n\t\tlogger, _ := testutils.NewLogger()\n\t\tmetricsFactory := metricstest.NewFactory(0)\n\t\ttracer, _, closer := tracerProvider(t)\n\t\tdefer closer()\n\n\t\t_, err := NewSpanReader(session, metricsFactory, logger, tracer.Tracer(\"test\"))\n\n\t\trequire.EqualError(t, err, \"neither table operation_names_v2 nor operation_names exist\")\n\t})\n}\n\nfunc TestSpanReaderGetServices(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tr.reader.serviceNamesReader = func() ([]string, error) { return []string{\"service-a\"}, nil }\n\t\ts, err := r.reader.GetServices(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, []string{\"service-a\"}, s)\n\t})\n}\n\nfunc TestSpanReaderGetOperations(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\texpectedOperations := []tracestore.Operation{\n\t\t\t{\n\t\t\t\tName:     \"operation-a\",\n\t\t\t\tSpanKind: \"server\",\n\t\t\t},\n\t\t}\n\t\tr.reader.operationNamesReader = func(_ tracestore.OperationQueryParams) ([]tracestore.Operation, error) {\n\t\t\treturn expectedOperations, nil\n\t\t}\n\t\ts, err := r.reader.GetOperationsV2(context.Background(),\n\t\t\ttracestore.OperationQueryParams{ServiceName: \"service-x\", SpanKind: \"server\"})\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expectedOperations, s)\n\t})\n}\n\nfunc TestSpanReaderGetTrace(t *testing.T) {\n\tbadScan := func() any {\n\t\treturn matchOnceWithSideEffect(func(args []any) {\n\t\t\tfor _, arg := range args {\n\t\t\t\tif v, ok := arg.(*[]dbmodel.KeyValue); ok {\n\t\t\t\t\t*v = []dbmodel.KeyValue{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tValueType: \"bad\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\ttestCases := []struct {\n\t\tscanner     any\n\t\tcloseErr    error\n\t\texpectedErr string\n\t}{\n\t\t{scanner: matchOnce()},\n\t\t{scanner: badScan(), expectedErr: \"invalid ValueType in\"},\n\t\t{\n\t\t\tscanner:     matchOnce(),\n\t\t\tcloseErr:    errors.New(\"error on close()\"),\n\t\t\texpectedErr: \"error reading traces from storage: error on close()\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(\"expected err=\"+testCase.expectedErr, func(t *testing.T) {\n\t\t\twithSpanReader(t, func(r *spanReaderTest) {\n\t\t\t\titer := &mocks.Iterator{}\n\t\t\t\titer.On(\"Scan\", testCase.scanner).Return(true)\n\t\t\t\titer.On(\"Scan\", mock.Anything).Return(false)\n\t\t\t\titer.On(\"Close\").Return(testCase.closeErr)\n\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery.On(\"Consistency\", cassandra.One).Return(query)\n\t\t\t\tquery.On(\"Iter\").Return(iter)\n\n\t\t\t\tr.session.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\n\t\t\t\ttrace, err := r.reader.GetTrace(context.Background(), spanstore.GetTraceParameters{})\n\t\t\t\tif testCase.expectedErr == \"\" {\n\t\t\t\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.NotNil(t, trace)\n\t\t\t\t} else {\n\t\t\t\t\trequire.ErrorContains(t, err, testCase.expectedErr)\n\t\t\t\t\tassert.Nil(t, trace)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestSpanReaderGetTrace_TraceNotFound(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\titer := &mocks.Iterator{}\n\t\titer.On(\"Scan\", mock.Anything).Return(false)\n\t\titer.On(\"Close\").Return(nil)\n\n\t\tquery := &mocks.Query{}\n\t\tquery.On(\"Consistency\", cassandra.One).Return(query)\n\t\tquery.On(\"Iter\").Return(iter)\n\n\t\tr.session.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\n\t\ttrace, err := r.reader.GetTrace(context.Background(), spanstore.GetTraceParameters{})\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\tassert.Nil(t, trace)\n\t\trequire.EqualError(t, err, \"trace not found\")\n\t})\n}\n\nfunc TestSpanReaderFindTracesBadRequest(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\t_, err := r.reader.FindTraces(context.Background(), nil)\n\t\trequire.Empty(t, r.traceBuffer.GetSpans(), \"Spans Not recorded\")\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestSpanReaderFindTraces(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption                           string\n\t\tnumTraces                         int\n\t\tqueryTags                         bool\n\t\tqueryOperation                    bool\n\t\tqueryDuration                     bool\n\t\tmainQueryError                    error\n\t\ttagsQueryError                    error\n\t\tserviceNameAndOperationQueryError error\n\t\tdurationQueryError                error\n\t\tloadQueryError                    error\n\t\texpectedCount                     int\n\t\texpectedError                     string\n\t\texpectedLogs                      []string\n\t}{\n\t\t{\n\t\t\tcaption:       \"main query\",\n\t\t\texpectedCount: 2,\n\t\t},\n\t\t{\n\t\t\tcaption:       \"tag query\",\n\t\t\texpectedCount: 2,\n\t\t\tqueryTags:     true,\n\t\t},\n\t\t{\n\t\t\tcaption:       \"with limit\",\n\t\t\tnumTraces:     1,\n\t\t\texpectedCount: 1,\n\t\t},\n\t\t{\n\t\t\tcaption:        \"main query error\",\n\t\t\tmainQueryError: errors.New(\"main query error\"),\n\t\t\texpectedError:  \"main query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failed to exec query\",\n\t\t\t\t\"main query error\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:        \"tags query error\",\n\t\t\tqueryTags:      true,\n\t\t\ttagsQueryError: errors.New(\"tags query error\"),\n\t\t\texpectedError:  \"tags query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failed to exec query\",\n\t\t\t\t\"tags query error\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:        \"operation name query\",\n\t\t\tqueryOperation: true,\n\t\t\tnumTraces:      0,\n\t\t\texpectedCount:  2,\n\t\t},\n\t\t{\n\t\t\tcaption:        \"operation name and tag query\",\n\t\t\tqueryTags:      true,\n\t\t\tqueryOperation: true,\n\t\t\texpectedCount:  2,\n\t\t},\n\t\t{\n\t\t\tcaption:                           \"operation name and tag error on operation query\",\n\t\t\tqueryTags:                         true,\n\t\t\tqueryOperation:                    true,\n\t\t\tserviceNameAndOperationQueryError: errors.New(\"operation query error\"),\n\t\t\texpectedError:                     \"operation query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failed to exec query\",\n\t\t\t\t\"operation query error\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:        \"operation name and tag error on tag query\",\n\t\t\tqueryTags:      true,\n\t\t\tqueryOperation: true,\n\t\t\ttagsQueryError: errors.New(\"tags query error\"),\n\t\t\texpectedError:  \"tags query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failed to exec query\",\n\t\t\t\t\"tags query error\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:       \"duration query\",\n\t\t\tqueryDuration: true,\n\t\t\tnumTraces:     1,\n\t\t\texpectedCount: 1,\n\t\t},\n\t\t{\n\t\t\tcaption:            \"duration query error\",\n\t\t\tqueryDuration:      true,\n\t\t\tdurationQueryError: errors.New(\"duration query error\"),\n\t\t\texpectedError:      \"duration query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failed to exec query\",\n\t\t\t\t\"duration query error\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:        \"load trace error\",\n\t\t\tloadQueryError: errors.New(\"load query error\"),\n\t\t\texpectedCount:  0,\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failure to read trace\",\n\t\t\t\t\"error reading traces from storage: load query error\",\n\t\t\t\t`\"trace_id\":\"0000000000000001\"`,\n\t\t\t\t`\"trace_id\":\"0000000000000002\"`,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithSpanReader(t, func(r *spanReaderTest) {\n\t\t\t\t// scanMatcher can match Iter.Scan() parameters and set trace ID fields\n\t\t\t\tscanMatcher := func(_ /* name */ string) any {\n\t\t\t\t\ttraceIDs := []dbmodel.TraceID{\n\t\t\t\t\t\tdbmodel.TraceIDFromDomain(model.NewTraceID(0, 1)),\n\t\t\t\t\t\tdbmodel.TraceIDFromDomain(model.NewTraceID(0, 2)),\n\t\t\t\t\t}\n\t\t\t\t\tscanFunc := func(args []any) bool {\n\t\t\t\t\t\tif len(traceIDs) == 0 {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, arg := range args {\n\t\t\t\t\t\t\tif ptr, ok := arg.(*dbmodel.TraceID); ok {\n\t\t\t\t\t\t\t\t*ptr = traceIDs[0]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttraceIDs = traceIDs[1:]\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn mock.MatchedBy(scanFunc)\n\t\t\t\t}\n\n\t\t\t\tmockQuery := func(queryErr error) *mocks.Query {\n\t\t\t\t\titer := &mocks.Iterator{}\n\t\t\t\t\titer.On(\"Scan\", scanMatcher(\"queryIter\")).Return(true)\n\t\t\t\t\titer.On(\"Scan\", mock.Anything).Return(false)\n\t\t\t\t\titer.On(\"Close\").Return(queryErr)\n\n\t\t\t\t\tquery := &mocks.Query{}\n\t\t\t\t\tquery.On(\"Bind\", mock.Anything).Return(query)\n\t\t\t\t\tquery.On(\"Consistency\", cassandra.One).Return(query)\n\t\t\t\t\tquery.On(\"PageSize\", 0).Return(query)\n\t\t\t\t\tquery.On(\"Iter\").Return(iter)\n\t\t\t\t\tquery.On(\"String\").Return(\"queryString\")\n\n\t\t\t\t\treturn query\n\t\t\t\t}\n\n\t\t\t\tmainQuery := mockQuery(testCase.mainQueryError)\n\t\t\t\ttagsQuery := mockQuery(testCase.tagsQueryError)\n\t\t\t\toperationQuery := mockQuery(testCase.serviceNameAndOperationQueryError)\n\t\t\t\tdurationQuery := mockQuery(testCase.durationQueryError)\n\n\t\t\t\tmakeLoadQuery := func() *mocks.Query {\n\t\t\t\t\tloadQueryIter := &mocks.Iterator{}\n\t\t\t\t\tloadQueryIter.On(\"Scan\", scanMatcher(\"loadIter\")).Return(true)\n\t\t\t\t\tloadQueryIter.On(\"Scan\", mock.Anything).Return(false)\n\t\t\t\t\tloadQueryIter.On(\"Close\").Return(testCase.loadQueryError)\n\n\t\t\t\t\tloadQuery := &mocks.Query{}\n\t\t\t\t\tloadQuery.On(\"Consistency\", cassandra.One).Return(loadQuery)\n\t\t\t\t\tloadQuery.On(\"Iter\").Return(loadQueryIter)\n\t\t\t\t\tloadQuery.On(\"PageSize\", mock.Anything).Return(loadQuery)\n\t\t\t\t\treturn loadQuery\n\t\t\t\t}\n\n\t\t\t\tr.session.On(\"Query\",\n\t\t\t\t\tqueryByServiceName,\n\t\t\t\t\tmock.Anything).Return(mainQuery)\n\t\t\t\tr.session.On(\"Query\",\n\t\t\t\t\tqueryByTag,\n\t\t\t\t\tmock.Anything).Return(tagsQuery)\n\t\t\t\tr.session.On(\"Query\",\n\t\t\t\t\tqueryByServiceAndOperationName,\n\t\t\t\t\tmock.Anything).Return(operationQuery)\n\t\t\t\tr.session.On(\"Query\",\n\t\t\t\t\tqueryByDuration,\n\t\t\t\t\tmock.Anything).Return(durationQuery)\n\t\t\t\tr.session.On(\"Query\",\n\t\t\t\t\tstringMatcher(\"SELECT trace_id\"),\n\t\t\t\t\tmatchOnce()).Return(makeLoadQuery())\n\t\t\t\tr.session.On(\"Query\",\n\t\t\t\t\tstringMatcher(\"SELECT trace_id\"),\n\t\t\t\t\tmock.Anything).Return(makeLoadQuery())\n\n\t\t\t\tqueryParams := &spanstore.TraceQueryParameters{\n\t\t\t\t\tServiceName:  \"service-a\",\n\t\t\t\t\tNumTraces:    100,\n\t\t\t\t\tStartTimeMax: time.Now(),\n\t\t\t\t\tStartTimeMin: time.Now().Add(-1 * time.Minute * 30),\n\t\t\t\t}\n\n\t\t\t\tqueryParams.NumTraces = testCase.numTraces\n\t\t\t\tif testCase.queryTags {\n\t\t\t\t\tqueryParams.Tags = make(map[string]string)\n\t\t\t\t\tqueryParams.Tags[\"x\"] = \"y\"\n\t\t\t\t}\n\t\t\t\tif testCase.queryOperation {\n\t\t\t\t\tqueryParams.OperationName = \"operation-b\"\n\t\t\t\t}\n\t\t\t\tif testCase.queryDuration {\n\t\t\t\t\tqueryParams.DurationMin = time.Minute\n\t\t\t\t\tqueryParams.DurationMax = time.Minute * 3\n\t\t\t\t}\n\t\t\t\tres, err := r.reader.FindTraces(context.Background(), queryParams)\n\t\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.Len(t, res, testCase.expectedCount, \"expecting certain number of traces\")\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\t}\n\t\t\t\tfor _, expectedLog := range testCase.expectedLogs {\n\t\t\t\t\tassert.Contains(t, r.logBuffer.String(), expectedLog)\n\t\t\t\t}\n\t\t\t\tif len(testCase.expectedLogs) == 0 {\n\t\t\t\t\tassert.Empty(t, r.logBuffer.String())\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestTraceQueryParameterValidation(t *testing.T) {\n\ttsp := &spanstore.TraceQueryParameters{\n\t\tServiceName: \"\",\n\t\tTags: map[string]string{\n\t\t\t\"michael\": \"jackson\",\n\t\t},\n\t}\n\terr := validateQuery(tsp)\n\trequire.EqualError(t, err, ErrServiceNameNotSet.Error())\n\n\ttsp.ServiceName = \"serviceName\"\n\ttsp.StartTimeMin = time.Now()\n\ttsp.StartTimeMax = time.Now().Add(-1 * time.Hour)\n\terr = validateQuery(tsp)\n\trequire.EqualError(t, err, ErrStartTimeMinGreaterThanMax.Error())\n\n\ttsp.StartTimeMin = time.Now().Add(-12 * time.Hour)\n\ttsp.DurationMin = time.Hour\n\ttsp.DurationMax = time.Minute\n\terr = validateQuery(tsp)\n\trequire.EqualError(t, err, ErrDurationMinGreaterThanMax.Error())\n\n\ttsp.DurationMin = time.Minute\n\ttsp.DurationMax = time.Hour\n\terr = validateQuery(tsp)\n\trequire.EqualError(t, err, ErrDurationAndTagQueryNotSupported.Error())\n\n\ttsp.StartTimeMin = time.Time{} // time.Unix(0,0) doesn't work because timezones\n\ttsp.StartTimeMax = time.Time{}\n\terr = validateQuery(tsp)\n\trequire.EqualError(t, err, ErrStartAndEndTimeNotSet.Error())\n}\n\nfunc TestGetOperations(t *testing.T) {\n\treader := SpanReader{}\n\t_, err := reader.GetOperations(context.Background(), spanstore.OperationQueryParameters{})\n\trequire.ErrorContains(t, err, \"not implemented\")\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/service_names.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/cache\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n)\n\nconst (\n\tinsertServiceName = `INSERT INTO service_names(service_name) VALUES (?)`\n\tqueryServiceNames = `SELECT service_name FROM service_names`\n)\n\n// ServiceNamesStorage stores known service names.\ntype ServiceNamesStorage struct {\n\tsession       cassandra.Session\n\twriteCacheTTL time.Duration\n\tInsertStmt    string\n\tQueryStmt     string\n\tmetrics       *casmetrics.Table\n\tserviceNames  cache.Cache\n\tlogger        *zap.Logger\n}\n\n// NewServiceNamesStorage returns a new ServiceNamesStorage\nfunc NewServiceNamesStorage(\n\tsession cassandra.Session,\n\twriteCacheTTL time.Duration,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n) *ServiceNamesStorage {\n\treturn &ServiceNamesStorage{\n\t\tsession:       session,\n\t\tInsertStmt:    insertServiceName,\n\t\tQueryStmt:     queryServiceNames,\n\t\tmetrics:       casmetrics.NewTable(metricsFactory, \"service_names\"),\n\t\twriteCacheTTL: writeCacheTTL,\n\t\tlogger:        logger,\n\t\tserviceNames: cache.NewLRUWithOptions(\n\t\t\t10000,\n\t\t\t&cache.Options{\n\t\t\t\tTTL:             writeCacheTTL,\n\t\t\t\tInitialCapacity: 1000,\n\t\t\t}),\n\t}\n}\n\n// Write saves a single service name\nfunc (s *ServiceNamesStorage) Write(serviceName string) error {\n\tvar err error\n\tquery := s.session.Query(s.InsertStmt)\n\tif inCache := checkWriteCache(serviceName, s.serviceNames, s.writeCacheTTL); !inCache {\n\t\tq := query.Bind(serviceName)\n\t\terr2 := s.metrics.Exec(q, s.logger)\n\t\tif err2 != nil {\n\t\t\terr = err2\n\t\t}\n\t}\n\treturn err\n}\n\n// checks if the key is in cache; returns true if it is, otherwise puts it there and returns false\nfunc checkWriteCache(key string, c cache.Cache, writeCacheTTL time.Duration) bool {\n\tif writeCacheTTL == 0 {\n\t\treturn false\n\t}\n\t// even though there is a race condition between Get and Put, it's not a problem for storage,\n\t// it simply means we might write the same service name twice.\n\tinCache := c.Get(key)\n\tif inCache == nil {\n\t\tc.Put(key, key)\n\t}\n\treturn inCache != nil\n}\n\n// GetServices returns all services traced by Jaeger\nfunc (s *ServiceNamesStorage) GetServices() ([]string, error) {\n\titer := s.session.Query(s.QueryStmt).Iter()\n\n\tvar service string\n\tvar services []string\n\tfor iter.Scan(&service) {\n\t\tservices = append(services, service)\n\t}\n\tif err := iter.Close(); err != nil {\n\t\terr = fmt.Errorf(\"error reading service_names from storage: %w\", err)\n\t\treturn nil, err\n\t}\n\treturn services, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/service_names_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype serviceNameStorageTest struct {\n\tsession        *mocks.Session\n\twriteCacheTTL  time.Duration\n\tmetricsFactory *metricstest.Factory\n\tlogger         *zap.Logger\n\tlogBuffer      *testutils.Buffer\n\tstorage        *ServiceNamesStorage\n}\n\nfunc withServiceNamesStorage(writeCacheTTL time.Duration, fn func(s *serviceNameStorageTest)) {\n\tsession := &mocks.Session{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(time.Second)\n\tdefer metricsFactory.Stop()\n\ts := &serviceNameStorageTest{\n\t\tsession:        session,\n\t\twriteCacheTTL:  writeCacheTTL,\n\t\tmetricsFactory: metricsFactory,\n\t\tlogger:         logger,\n\t\tlogBuffer:      logBuffer,\n\t\tstorage:        NewServiceNamesStorage(session, writeCacheTTL, metricsFactory, logger),\n\t}\n\tfn(s)\n}\n\nfunc TestServiceNamesStorageWrite(t *testing.T) {\n\tfor _, ttl := range []time.Duration{0, time.Minute} {\n\t\twriteCacheTTL := ttl // capture loop var\n\t\tt.Run(fmt.Sprintf(\"writeCacheTTL=%v\", writeCacheTTL), func(t *testing.T) {\n\t\t\twithServiceNamesStorage(writeCacheTTL, func(s *serviceNameStorageTest) {\n\t\t\t\texecError := errors.New(\"exec error\")\n\t\t\t\tquery := &mocks.Query{}\n\t\t\t\tquery1 := &mocks.Query{}\n\t\t\t\tquery2 := &mocks.Query{}\n\t\t\t\tquery.On(\"Bind\", []any{\"service-a\"}).Return(query1)\n\t\t\t\tquery.On(\"Bind\", []any{\"service-b\"}).Return(query2)\n\t\t\t\tquery1.On(\"Exec\").Return(nil)\n\t\t\t\tquery2.On(\"Exec\").Return(execError)\n\t\t\t\tquery2.On(\"String\").Return(\"select from service_names\")\n\n\t\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\")).Return(query)\n\n\t\t\t\terr := s.storage.Write(\"service-a\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\terr = s.storage.Write(\"service-b\")\n\t\t\t\trequire.EqualError(t, err, \"failed to Exec query 'select from service_names': exec error\")\n\t\t\t\tassert.Equal(t, map[string]string{\n\t\t\t\t\t\"level\": \"error\",\n\t\t\t\t\t\"msg\":   \"Failed to exec query\",\n\t\t\t\t\t\"query\": \"select from service_names\",\n\t\t\t\t\t\"error\": \"exec error\",\n\t\t\t\t}, s.logBuffer.JSONLine(0))\n\n\t\t\t\tcounts, _ := s.metricsFactory.Snapshot()\n\t\t\t\tassert.Equal(t, map[string]int64{\n\t\t\t\t\t\"attempts|table=service_names\": 2, \"inserts|table=service_names\": 1, \"errors|table=service_names\": 1,\n\t\t\t\t}, counts)\n\n\t\t\t\t// write again\n\t\t\t\terr = s.storage.Write(\"service-a\")\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tcounts2, _ := s.metricsFactory.Snapshot()\n\t\t\t\texpCounts := counts\n\t\t\t\tif writeCacheTTL == 0 {\n\t\t\t\t\t// without write cache, the second write must succeed\n\t\t\t\t\texpCounts[\"attempts|table=service_names\"]++\n\t\t\t\t\texpCounts[\"inserts|table=service_names\"]++\n\t\t\t\t}\n\t\t\t\tassert.Equal(t, expCounts, counts2)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestServiceNamesStorageGetServices(t *testing.T) {\n\tscanError := errors.New(\"scan error\")\n\tvar writeCacheTTL time.Duration\n\tvar matched bool\n\tmatchOnce := mock.MatchedBy(func(_ []any) bool {\n\t\tif matched {\n\t\t\treturn false\n\t\t}\n\t\tmatched = true\n\t\treturn true\n\t})\n\tmatchEverything := mock.MatchedBy(func(_ []any) bool { return true })\n\tfor _, expErr := range []error{nil, scanError} {\n\t\twithServiceNamesStorage(writeCacheTTL, func(s *serviceNameStorageTest) {\n\t\t\titer := &mocks.Iterator{}\n\t\t\titer.On(\"Scan\", matchOnce).Return(true)\n\t\t\titer.On(\"Scan\", matchEverything).Return(false) // false to stop the loop\n\t\t\titer.On(\"Close\").Return(expErr)\n\n\t\t\tquery := &mocks.Query{}\n\t\t\tquery.On(\"Iter\").Return(iter)\n\n\t\t\ts.session.On(\"Query\", mock.AnythingOfType(\"string\")).Return(query)\n\n\t\t\tservices, err := s.storage.GetServices()\n\t\t\tif expErr == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\t// expect empty string because mock iter.Scan(&placeholder) does not write to `placeholder`\n\t\t\t\tassert.Equal(t, []string{\"\"}, services)\n\t\t\t} else {\n\t\t\t\trequire.EqualError(t, err, \"error reading service_names from storage: \"+expErr.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/writer.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra\"\n\tcasmetrics \"github.com/jaegertracing/jaeger/internal/storage/cassandra/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n)\n\nconst (\n\tinsertSpan = `\n\t\tINSERT\n\t\tINTO traces(trace_id, span_id, span_hash, parent_id, operation_name, flags,\n\t\t\t\t    start_time, duration, tags, logs, refs, process)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n\n\tserviceNameIndex = `\n\t\tINSERT\n\t\tINTO service_name_index(service_name, bucket, start_time, trace_id)\n\t\tVALUES (?, ?, ?, ?)`\n\n\tserviceOperationIndex = `\n\t\tINSERT\n\t\tINTO\n\t\tservice_operation_index(service_name, operation_name, start_time, trace_id)\n\t\tVALUES (?, ?, ?, ?)`\n\n\ttagIndex = `\n\t\tINSERT\n\t\tINTO tag_index(trace_id, span_id, service_name, start_time, tag_key, tag_value)\n\t\tVALUES (?, ?, ?, ?, ?, ?)`\n\n\tdurationIndex = `\n\t\tINSERT\n\t\tINTO duration_index(service_name, operation_name, bucket, duration, start_time, trace_id)\n\t\tVALUES (?, ?, ?, ?, ?, ?)`\n\n\tmaximumTagKeyOrValueSize = 256\n\n\t// DefaultNumBuckets Number of buckets for bucketed keys\n\tdefaultNumBuckets = 10\n\n\tdurationBucketSize = time.Hour\n)\n\nconst (\n\tstoreFlag = storageMode(1 << iota)\n\tindexFlag\n)\n\ntype (\n\tstorageMode          uint8\n\tserviceNamesWriter   func(serviceName string) error\n\toperationNamesWriter func(operation dbmodel.Operation) error\n)\n\ntype spanWriterMetrics struct {\n\ttraces                *casmetrics.Table\n\ttagIndex              *casmetrics.Table\n\tserviceNameIndex      *casmetrics.Table\n\tserviceOperationIndex *casmetrics.Table\n\tdurationIndex         *casmetrics.Table\n}\n\n// SpanWriter handles all writes to Cassandra for the Jaeger data model\ntype SpanWriter struct {\n\tsession              cassandra.Session\n\tserviceNamesWriter   serviceNamesWriter\n\toperationNamesWriter operationNamesWriter\n\twriterMetrics        spanWriterMetrics\n\tlogger               *zap.Logger\n\ttagIndexSkipped      metrics.Counter\n\ttagFilter            dbmodel.TagFilter\n\tstorageMode          storageMode\n\tindexFilter          dbmodel.IndexFilter\n}\n\n// NewSpanWriter returns a SpanWriter\nfunc NewSpanWriter(\n\tsession cassandra.Session,\n\twriteCacheTTL time.Duration,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n\toptions ...Option,\n) (*SpanWriter, error) {\n\tserviceNamesStorage := NewServiceNamesStorage(session, writeCacheTTL, metricsFactory, logger)\n\toperationNamesStorage, err := NewOperationNamesStorage(session, writeCacheTTL, metricsFactory, logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttagIndexSkipped := metricsFactory.Counter(metrics.Options{Name: \"tag_index_skipped\", Tags: nil})\n\topts := applyOptions(options...)\n\treturn &SpanWriter{\n\t\tsession:              session,\n\t\tserviceNamesWriter:   serviceNamesStorage.Write,\n\t\toperationNamesWriter: operationNamesStorage.Write,\n\t\twriterMetrics: spanWriterMetrics{\n\t\t\ttraces:                casmetrics.NewTable(metricsFactory, \"traces\"),\n\t\t\ttagIndex:              casmetrics.NewTable(metricsFactory, \"tag_index\"),\n\t\t\tserviceNameIndex:      casmetrics.NewTable(metricsFactory, \"service_name_index\"),\n\t\t\tserviceOperationIndex: casmetrics.NewTable(metricsFactory, \"service_operation_index\"),\n\t\t\tdurationIndex:         casmetrics.NewTable(metricsFactory, \"duration_index\"),\n\t\t},\n\t\tlogger:          logger,\n\t\ttagIndexSkipped: tagIndexSkipped,\n\t\ttagFilter:       opts.tagFilter,\n\t\tstorageMode:     opts.storageMode,\n\t\tindexFilter:     opts.indexFilter,\n\t}, nil\n}\n\n// Close closes SpanWriter\nfunc (s *SpanWriter) Close() error {\n\ts.session.Close()\n\treturn nil\n}\n\n// WriteSpan saves the span into Cassandra\nfunc (s *SpanWriter) WriteSpan(_ context.Context, span *model.Span) error {\n\tds := dbmodel.FromDomain(span)\n\tif s.storageMode&storeFlag == storeFlag {\n\t\tif err := s.writeSpanToDB(span, ds); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif s.storageMode&indexFlag == indexFlag {\n\t\tif err := s.writeIndexes(span, ds); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SpanWriter) writeSpanToDB(_ *model.Span, ds *dbmodel.Span) error {\n\tmainQuery := s.session.Query(\n\t\tinsertSpan,\n\t\tds.TraceID,\n\t\tds.SpanID,\n\t\tds.SpanHash,\n\t\tds.ParentID,\n\t\tds.OperationName,\n\t\tds.Flags,\n\t\tds.StartTime,\n\t\tds.Duration,\n\t\tds.Tags,\n\t\tds.Logs,\n\t\tds.Refs,\n\t\tds.Process,\n\t)\n\tif err := s.writerMetrics.traces.Exec(mainQuery, s.logger); err != nil {\n\t\treturn s.logError(ds, err, \"Failed to insert span\", s.logger)\n\t}\n\treturn nil\n}\n\nfunc (s *SpanWriter) writeIndexes(span *model.Span, ds *dbmodel.Span) error {\n\tspanKind, _ := span.GetSpanKind() // if not found it returns \"\"\n\tif err := s.saveServiceNameAndOperationName(dbmodel.Operation{\n\t\tServiceName:   ds.ServiceName,\n\t\tSpanKind:      string(spanKind),\n\t\tOperationName: ds.OperationName,\n\t}); err != nil {\n\t\t// should this be a soft failure?\n\t\treturn s.logError(ds, err, \"Failed to insert service name and operation name\", s.logger)\n\t}\n\n\tif s.indexFilter(ds, dbmodel.ServiceIndex) {\n\t\tif err := s.indexByService(ds); err != nil {\n\t\t\treturn s.logError(ds, err, \"Failed to index service name\", s.logger)\n\t\t}\n\t}\n\n\tif s.indexFilter(ds, dbmodel.OperationIndex) {\n\t\tif err := s.indexByOperation(ds); err != nil {\n\t\t\treturn s.logError(ds, err, \"Failed to index operation name\", s.logger)\n\t\t}\n\t}\n\n\tif span.Flags.IsFirehoseEnabled() {\n\t\treturn nil // skipping expensive indexing\n\t}\n\n\tif err := s.indexByTags(ds); err != nil {\n\t\treturn s.logError(ds, err, \"Failed to index tags\", s.logger)\n\t}\n\n\tif s.indexFilter(ds, dbmodel.DurationIndex) {\n\t\tif err := s.indexByDuration(ds, span.StartTime); err != nil {\n\t\t\treturn s.logError(ds, err, \"Failed to index duration\", s.logger)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SpanWriter) indexByTags(ds *dbmodel.Span) error {\n\tfor _, v := range dbmodel.GetAllUniqueTags(ds, s.tagFilter) {\n\t\t// we should introduce retries or just ignore failures imo, retrying each individual tag insertion might be better\n\t\t// we should consider bucketing.\n\t\tif s.shouldIndexTag(v) {\n\t\t\tinsertTagQuery := s.session.Query(tagIndex, ds.TraceID, ds.SpanID, v.ServiceName, ds.StartTime, v.TagKey, v.TagValue)\n\t\t\tif err := s.writerMetrics.tagIndex.Exec(insertTagQuery, s.logger); err != nil {\n\t\t\t\twithTagInfo := s.logger.\n\t\t\t\t\tWith(zap.String(\"tag_key\", v.TagKey)).\n\t\t\t\t\tWith(zap.String(\"tag_value\", v.TagValue)).\n\t\t\t\t\tWith(zap.String(\"service_name\", v.ServiceName))\n\t\t\t\treturn s.logError(ds, err, \"Failed to index tag\", withTagInfo)\n\t\t\t}\n\t\t} else {\n\t\t\ts.tagIndexSkipped.Inc(1)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SpanWriter) indexByDuration(span *dbmodel.Span, startTime time.Time) error {\n\tquery := s.session.Query(durationIndex)\n\ttimeBucket := startTime.Round(durationBucketSize)\n\tvar err error\n\tindexByOperationName := func(operationName string) {\n\t\tq1 := query.Bind(span.Process.ServiceName, operationName, timeBucket, span.Duration, span.StartTime, span.TraceID)\n\t\tif err2 := s.writerMetrics.durationIndex.Exec(q1, s.logger); err2 != nil {\n\t\t\t_ = s.logError(span, err2, \"Cannot index duration\", s.logger)\n\t\t\terr = err2\n\t\t}\n\t}\n\tindexByOperationName(\"\")                 // index by service name alone\n\tindexByOperationName(span.OperationName) // index by service name and operation name\n\treturn err\n}\n\nfunc (s *SpanWriter) indexByService(span *dbmodel.Span) error {\n\t//nolint:gosec // G115\n\tbucketNo := uint64(span.SpanHash) % defaultNumBuckets\n\tquery := s.session.Query(serviceNameIndex)\n\tq := query.Bind(span.Process.ServiceName, bucketNo, span.StartTime, span.TraceID)\n\treturn s.writerMetrics.serviceNameIndex.Exec(q, s.logger)\n}\n\nfunc (s *SpanWriter) indexByOperation(span *dbmodel.Span) error {\n\tquery := s.session.Query(serviceOperationIndex)\n\tq := query.Bind(span.Process.ServiceName, span.OperationName, span.StartTime, span.TraceID)\n\treturn s.writerMetrics.serviceOperationIndex.Exec(q, s.logger)\n}\n\n// shouldIndexTag checks to see if the tag is json or not, if it's UTF8 valid and it's not too large\nfunc (*SpanWriter) shouldIndexTag(tag dbmodel.TagInsertion) bool {\n\tisJSON := func(s string) bool {\n\t\tvar js json.RawMessage\n\t\t// poor man's string-is-a-json check shortcircuits full unmarshalling\n\t\treturn strings.HasPrefix(s, \"{\") && json.Unmarshal([]byte(s), &js) == nil\n\t}\n\n\treturn len(tag.TagKey) < maximumTagKeyOrValueSize &&\n\t\tlen(tag.TagValue) < maximumTagKeyOrValueSize &&\n\t\tutf8.ValidString(tag.TagValue) &&\n\t\tutf8.ValidString(tag.TagKey) &&\n\t\t!isJSON(tag.TagValue)\n}\n\nfunc (*SpanWriter) logError(span *dbmodel.Span, err error, msg string, logger *zap.Logger) error {\n\tlogger.\n\t\tWith(zap.String(\"trace_id\", span.TraceID.String())).\n\t\tWith(zap.Int64(\"span_id\", span.SpanID)).\n\t\tWith(zap.Error(err)).\n\t\tError(msg)\n\treturn fmt.Errorf(\"%s: %w\", msg, err)\n}\n\nfunc (s *SpanWriter) saveServiceNameAndOperationName(operation dbmodel.Operation) error {\n\tif err := s.serviceNamesWriter(operation.ServiceName); err != nil {\n\t\treturn err\n\t}\n\treturn s.operationNamesWriter(operation)\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/writer_options.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n)\n\n// Option is a function that sets some option on the writer.\ntype Option func(c *Options)\n\n// Options control behavior of the writer.\ntype Options struct {\n\ttagFilter   dbmodel.TagFilter\n\tstorageMode storageMode\n\tindexFilter dbmodel.IndexFilter\n}\n\n// TagFilter can be provided to filter any tags that should not be indexed.\nfunc TagFilter(tagFilter dbmodel.TagFilter) Option {\n\treturn func(o *Options) {\n\t\to.tagFilter = tagFilter\n\t}\n}\n\n// StoreIndexesOnly can be provided to skip storing spans, and only store span indexes.\nfunc StoreIndexesOnly() Option {\n\treturn func(o *Options) {\n\t\to.storageMode = indexFlag\n\t}\n}\n\n// StoreWithoutIndexing can be provided to store spans without indexing them.\nfunc StoreWithoutIndexing() Option {\n\treturn func(o *Options) {\n\t\to.storageMode = storeFlag\n\t}\n}\n\n// IndexFilter can be provided to filter certain spans that should not be indexed.\nfunc IndexFilter(indexFilter dbmodel.IndexFilter) Option {\n\treturn func(o *Options) {\n\t\to.indexFilter = indexFilter\n\t}\n}\n\nfunc applyOptions(opts ...Option) Options {\n\to := Options{}\n\tfor _, opt := range opts {\n\t\topt(&o)\n\t}\n\tif o.tagFilter == nil {\n\t\to.tagFilter = dbmodel.DefaultTagFilter\n\t}\n\tif o.storageMode == 0 {\n\t\to.storageMode = storeFlag | indexFlag\n\t}\n\tif o.indexFilter == nil {\n\t\to.indexFilter = dbmodel.DefaultIndexFilter\n\t}\n\treturn o\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/writer_options_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n)\n\nfunc TestWriterOptions(t *testing.T) {\n\topts := applyOptions(TagFilter(dbmodel.DefaultTagFilter), IndexFilter(dbmodel.DefaultIndexFilter))\n\tassert.Equal(t, dbmodel.DefaultTagFilter, opts.tagFilter)\n\tassert.ObjectsAreEqual(dbmodel.DefaultIndexFilter, opts.indexFilter)\n}\n\nfunc TestWriterOptions_StorageMode(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\texpected storageMode\n\t\topts     Options\n\t}{\n\t\t{\n\t\t\tname:     \"Default\",\n\t\t\texpected: indexFlag | storeFlag,\n\t\t\topts:     applyOptions(),\n\t\t},\n\t\t{\n\t\t\tname:     \"Index Only\",\n\t\t\texpected: indexFlag,\n\t\t\topts:     applyOptions(StoreIndexesOnly()),\n\t\t},\n\t\t{\n\t\t\tname:     \"Store Only\",\n\t\t\texpected: storeFlag,\n\t\t\topts:     applyOptions(StoreWithoutIndexing()),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tt.expected, tt.opts.storageMode)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/cassandra/spanstore/writer_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype spanWriterTest struct {\n\tsession   *mocks.Session\n\tlogger    *zap.Logger\n\tlogBuffer *testutils.Buffer\n\twriter    *SpanWriter\n}\n\nfunc withSpanWriter(t *testing.T, writeCacheTTL time.Duration, fn func(w *spanWriterTest), options ...Option,\n) {\n\tsession := &mocks.Session{}\n\tquery := &mocks.Query{}\n\tsession.On(\"Query\",\n\t\tfmt.Sprintf(tableCheckStmt, schemas[latestVersion].tableName),\n\t\tmock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(nil)\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\twriter, err := NewSpanWriter(session, writeCacheTTL, metricsFactory, logger, options...)\n\trequire.NoError(t, err)\n\tw := &spanWriterTest{\n\t\tsession:   session,\n\t\tlogger:    logger,\n\t\tlogBuffer: logBuffer,\n\t\twriter:    writer,\n\t}\n\tfn(w)\n}\n\nvar _ spanstore.Writer = &SpanWriter{} // check API conformance\n\nfunc TestNewSpanWriter(t *testing.T) {\n\tt.Run(\"test span writer creation\", func(t *testing.T) {\n\t\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\t\tassert.NotNil(t, w.writer)\n\t\t})\n\t})\n\n\tt.Run(\"test span writer creation error\", func(t *testing.T) {\n\t\tsession := &mocks.Session{}\n\t\tquery := &mocks.Query{}\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[latestVersion].tableName),\n\t\t\tmock.Anything).Return(query)\n\t\tsession.On(\"Query\",\n\t\t\tfmt.Sprintf(tableCheckStmt, schemas[previousVersion].tableName),\n\t\t\tmock.Anything).Return(query)\n\t\tquery.On(\"Exec\").Return(errors.New(\"table does not exist\"))\n\t\tlogger, _ := testutils.NewLogger()\n\t\tmetricsFactory := metricstest.NewFactory(0)\n\n\t\t_, err := NewSpanWriter(session, 0, metricsFactory, logger)\n\n\t\tassert.EqualError(t, err, \"neither table operation_names_v2 nor operation_names exist\")\n\t})\n}\n\nfunc TestClientClose(t *testing.T) {\n\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\tw.session.On(\"Close\").Return(nil)\n\t\tw.writer.Close()\n\t\tw.session.AssertNumberOfCalls(t, \"Close\", 1)\n\t})\n}\n\nfunc TestSpanWriter(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption                        string\n\t\tfirehose                       bool\n\t\tmainQueryError                 error\n\t\ttagsQueryError                 error\n\t\tserviceNameQueryError          error\n\t\tserviceOperationNameQueryError error\n\t\tdurationNoOperationQueryError  error\n\t\tserviceNameError               error\n\t\texpectedError                  string\n\t\texpectedLogs                   []string\n\t}{\n\t\t{\n\t\t\tcaption: \"main query\",\n\t\t},\n\t\t{\n\t\t\tcaption:  \"main firehose query\",\n\t\t\tfirehose: true,\n\t\t},\n\t\t{\n\t\t\tcaption:        \"main query error\",\n\t\t\tmainQueryError: errors.New(\"main query error\"),\n\t\t\texpectedError:  \"Failed to insert span: failed to Exec query 'select from traces': main query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t`\"msg\":\"Failed to exec query\"`,\n\t\t\t\t`\"query\":\"select from traces\"`,\n\t\t\t\t`\"error\":\"main query error\"`,\n\t\t\t\t\"Failed to insert span\",\n\t\t\t\t`\"trace_id\":\"0000000000000001\"`,\n\t\t\t\t`\"span_id\":0`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:        \"tags query error\",\n\t\t\ttagsQueryError: errors.New(\"tags query error\"),\n\t\t\texpectedError:  \"Failed to index tags: Failed to index tag: failed to Exec query 'select from tags': tags query error\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t`\"msg\":\"Failed to exec query\"`,\n\t\t\t\t`\"query\":\"select from tags\"`,\n\t\t\t\t`\"error\":\"tags query error\"`,\n\t\t\t\t\"Failed to index tags\",\n\t\t\t\t`\"tag_key\":\"x\"`,\n\t\t\t\t`\"tag_value\":\"y\"`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:          \"save service name query error\",\n\t\t\tserviceNameError: errors.New(\"serviceNameError\"),\n\t\t\texpectedError:    \"Failed to insert service name and operation name: serviceNameError\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t\"Failed to insert service name and operation name\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:               \"add span to service name index\",\n\t\t\tserviceNameQueryError: errors.New(\"serviceNameQueryError\"),\n\t\t\texpectedError:         \"Failed to index service name: failed to Exec query 'select from service_name_index': serviceNameQueryError\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t`\"msg\":\"Failed to exec query\"`,\n\t\t\t\t`\"query\":\"select from service_name_index\"`,\n\t\t\t\t`\"error\":\"serviceNameQueryError\"`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:                        \"add span to operation name index\",\n\t\t\tserviceOperationNameQueryError: errors.New(\"serviceOperationNameQueryError\"),\n\t\t\texpectedError:                  \"Failed to index operation name: failed to Exec query 'select from service_operation_index': serviceOperationNameQueryError\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t`\"msg\":\"Failed to exec query\"`,\n\t\t\t\t`\"query\":\"select from service_operation_index\"`,\n\t\t\t\t`\"error\":\"serviceOperationNameQueryError\"`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:                       \"add duration with no operation name\",\n\t\t\tdurationNoOperationQueryError: errors.New(\"durationNoOperationError\"),\n\t\t\texpectedError:                 \"Failed to index duration: failed to Exec query 'select from duration_index': durationNoOperationError\",\n\t\t\texpectedLogs: []string{\n\t\t\t\t`\"msg\":\"Failed to exec query\"`,\n\t\t\t\t`\"query\":\"select from duration_index\"`,\n\t\t\t\t`\"error\":\"durationNoOperationError\"`,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\t\t\tspan := &model.Span{\n\t\t\t\t\tTraceID:       model.NewTraceID(0, 1),\n\t\t\t\t\tOperationName: \"operation-a\",\n\t\t\t\t\tTags: model.KeyValues{\n\t\t\t\t\t\tmodel.String(\"x\", \"y\"),\n\t\t\t\t\t\tmodel.String(\"json\", `{\"x\":\"y\"}`), // string tag with json value will not be inserted\n\t\t\t\t\t},\n\t\t\t\t\tProcess: &model.Process{\n\t\t\t\t\t\tServiceName: \"service-a\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tif testCase.firehose {\n\t\t\t\t\tspan.Flags = model.FirehoseFlag\n\t\t\t\t}\n\n\t\t\t\tspanQuery := &mocks.Query{}\n\t\t\t\tspanQuery.On(\"Bind\", mock.Anything).Return(spanQuery)\n\t\t\t\tspanQuery.On(\"Exec\").Return(testCase.mainQueryError)\n\t\t\t\tspanQuery.On(\"String\").Return(\"select from traces\")\n\n\t\t\t\tserviceNameQuery := &mocks.Query{}\n\t\t\t\tserviceNameQuery.On(\"Bind\", mock.Anything).Return(serviceNameQuery)\n\t\t\t\tserviceNameQuery.On(\"Exec\").Return(testCase.serviceNameQueryError)\n\t\t\t\tserviceNameQuery.On(\"String\").Return(\"select from service_name_index\")\n\n\t\t\t\tserviceOperationNameQuery := &mocks.Query{}\n\t\t\t\tserviceOperationNameQuery.On(\"Bind\", mock.Anything).Return(serviceOperationNameQuery)\n\t\t\t\tserviceOperationNameQuery.On(\"Exec\").Return(testCase.serviceOperationNameQueryError)\n\t\t\t\tserviceOperationNameQuery.On(\"String\").Return(\"select from service_operation_index\")\n\n\t\t\t\ttagsQuery := &mocks.Query{}\n\t\t\t\ttagsQuery.On(\"Exec\").Return(testCase.tagsQueryError)\n\t\t\t\ttagsQuery.On(\"String\").Return(\"select from tags\")\n\n\t\t\t\tdurationNoOperationQuery := &mocks.Query{}\n\t\t\t\tdurationNoOperationQuery.On(\"Bind\", mock.Anything).Return(durationNoOperationQuery)\n\t\t\t\tdurationNoOperationQuery.On(\"Exec\").Return(testCase.durationNoOperationQueryError)\n\t\t\t\tdurationNoOperationQuery.On(\"String\").Return(\"select from duration_index\")\n\n\t\t\t\t// Define expected queries\n\t\t\t\tw.session.On(\"Query\", insertSpan, mock.Anything).Return(spanQuery)\n\t\t\t\tw.session.On(\"Query\", serviceNameIndex).Return(serviceNameQuery)\n\t\t\t\tw.session.On(\"Query\", serviceOperationIndex, mock.Anything).Return(serviceOperationNameQuery)\n\t\t\t\t// note: using Once() below because we only want one tag to be inserted\n\t\t\t\tw.session.On(\"Query\", tagIndex, mock.Anything).Return(tagsQuery).Once()\n\t\t\t\tw.session.On(\"Query\", durationIndex).Return(durationNoOperationQuery).Once()\n\n\t\t\t\tw.writer.serviceNamesWriter = func(_ /* serviceName */ string) error { return testCase.serviceNameError }\n\t\t\t\tw.writer.operationNamesWriter = func(_ dbmodel.Operation) error { return testCase.serviceNameError }\n\t\t\t\terr := w.writer.WriteSpan(context.Background(), span)\n\n\t\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\t}\n\t\t\t\tfor _, expectedLog := range testCase.expectedLogs {\n\t\t\t\t\tassert.Contains(t, w.logBuffer.String(), expectedLog)\n\t\t\t\t}\n\t\t\t\tif len(testCase.expectedLogs) == 0 {\n\t\t\t\t\tassert.Empty(t, w.logBuffer.String())\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestSpanWriterSaveServiceNameAndOperationName(t *testing.T) {\n\texpectedErr := errors.New(\"some error\")\n\ttestCases := []struct {\n\t\tserviceNamesWriter   serviceNamesWriter\n\t\toperationNamesWriter operationNamesWriter\n\t\texpectedError        string\n\t}{\n\t\t{\n\t\t\tserviceNamesWriter:   func(_ /* serviceName */ string) error { return nil },\n\t\t\toperationNamesWriter: func(_ dbmodel.Operation) error { return nil },\n\t\t},\n\t\t{\n\t\t\tserviceNamesWriter:   func(_ /* serviceName */ string) error { return expectedErr },\n\t\t\toperationNamesWriter: func(_ dbmodel.Operation) error { return nil },\n\t\t\texpectedError:        \"some error\",\n\t\t},\n\t\t{\n\t\t\tserviceNamesWriter:   func(_ /* serviceName */ string) error { return nil },\n\t\t\toperationNamesWriter: func(_ dbmodel.Operation) error { return expectedErr },\n\t\t\texpectedError:        \"some error\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\t\tw.writer.serviceNamesWriter = testCase.serviceNamesWriter\n\t\t\tw.writer.operationNamesWriter = testCase.operationNamesWriter\n\t\t\terr := w.writer.saveServiceNameAndOperationName(\n\t\t\t\tdbmodel.Operation{\n\t\t\t\t\tServiceName:   \"service\",\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t})\n\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSpanWriterSkippingTags(t *testing.T) {\n\tlongString := strings.Repeat(\"x\", 300)\n\ttestCases := []struct {\n\t\tkey    string\n\t\tvalue  string\n\t\tinsert bool\n\t}{\n\t\t{key: \"x\", value: \"y\", insert: true},\n\t\t{key: longString, value: \"y\", insert: false},\n\t\t{key: \"x\", value: longString, insert: false},\n\t\t{key: \"x\", value: `{\"x\":\"y\"}`, insert: false}, // value is a JSON\n\t\t{key: \"x\", value: `{\"x\":`, insert: true},      // value is not a JSON\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc // capture loop var\n\t\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\t\tdb := dbmodel.TagInsertion{\n\t\t\t\tServiceName: \"service-a\",\n\t\t\t\tTagKey:      testCase.key,\n\t\t\t\tTagValue:    testCase.value,\n\t\t\t}\n\t\t\tok := w.writer.shouldIndexTag(db)\n\t\t\tassert.Equal(t, testCase.insert, ok)\n\t\t})\n\t}\n}\n\nfunc TestStorageMode_IndexOnly(t *testing.T) {\n\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\tw.writer.serviceNamesWriter = func(_ /* serviceName */ string) error { return nil }\n\t\tw.writer.operationNamesWriter = func(_ dbmodel.Operation) error { return nil }\n\t\tspan := &model.Span{\n\t\t\tTraceID: model.NewTraceID(0, 1),\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"service-a\",\n\t\t\t},\n\t\t}\n\n\t\tserviceNameQuery := &mocks.Query{}\n\t\tserviceNameQuery.On(\"Bind\", mock.Anything).Return(serviceNameQuery)\n\t\tserviceNameQuery.On(\"Exec\").Return(nil)\n\n\t\tserviceOperationNameQuery := &mocks.Query{}\n\t\tserviceOperationNameQuery.On(\"Bind\", mock.Anything).Return(serviceOperationNameQuery)\n\t\tserviceOperationNameQuery.On(\"Exec\").Return(nil)\n\n\t\tdurationNoOperationQuery := &mocks.Query{}\n\t\tdurationNoOperationQuery.On(\"Bind\", mock.Anything).Return(durationNoOperationQuery)\n\t\tdurationNoOperationQuery.On(\"Exec\").Return(nil)\n\n\t\tw.session.On(\"Query\", serviceNameIndex).Return(serviceNameQuery)\n\t\tw.session.On(\"Query\", serviceOperationIndex).Return(serviceOperationNameQuery)\n\t\tw.session.On(\"Query\", durationIndex).Return(durationNoOperationQuery).Once()\n\n\t\terr := w.writer.WriteSpan(context.Background(), span)\n\n\t\trequire.NoError(t, err)\n\t\tserviceNameQuery.AssertExpectations(t)\n\t\tserviceOperationNameQuery.AssertExpectations(t)\n\t\tdurationNoOperationQuery.AssertExpectations(t)\n\t\tw.session.AssertExpectations(t)\n\t\tw.session.AssertNotCalled(t, \"Query\", insertSpan, mock.Anything)\n\t}, StoreIndexesOnly())\n}\n\nvar filterEverything = func(*dbmodel.Span, int) bool {\n\treturn false\n}\n\nfunc TestStorageMode_IndexOnly_WithFilter(t *testing.T) {\n\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\tw.writer.indexFilter = filterEverything\n\t\tw.writer.serviceNamesWriter = func(_ /* serviceName */ string) error { return nil }\n\t\tw.writer.operationNamesWriter = func(_ dbmodel.Operation) error { return nil }\n\t\tspan := &model.Span{\n\t\t\tTraceID: model.NewTraceID(0, 1),\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"service-a\",\n\t\t\t},\n\t\t}\n\t\terr := w.writer.WriteSpan(context.Background(), span)\n\t\trequire.NoError(t, err)\n\t\tw.session.AssertExpectations(t)\n\t\tw.session.AssertNotCalled(t, \"Query\", serviceOperationIndex, mock.Anything)\n\t\tw.session.AssertNotCalled(t, \"Query\", serviceNameIndex, mock.Anything)\n\t\tw.session.AssertNotCalled(t, \"Query\", durationIndex, mock.Anything)\n\t}, StoreIndexesOnly())\n}\n\nfunc TestStorageMode_IndexOnly_FirehoseSpan(t *testing.T) {\n\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\tvar serviceWritten atomic.Pointer[string]\n\t\tvar operationWritten atomic.Pointer[dbmodel.Operation]\n\t\tempty := \"\"\n\t\tserviceWritten.Store(&empty)\n\t\toperationWritten.Store(&dbmodel.Operation{})\n\t\tw.writer.serviceNamesWriter = func(serviceName string) error {\n\t\t\tserviceWritten.Store(&serviceName)\n\t\t\treturn nil\n\t\t}\n\t\tw.writer.operationNamesWriter = func(operation dbmodel.Operation) error {\n\t\t\toperationWritten.Store(&operation)\n\t\t\treturn nil\n\t\t}\n\t\tspan := &model.Span{\n\t\t\tTraceID:       model.NewTraceID(0, 1),\n\t\t\tOperationName: \"package-delivery\",\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"planet-express\",\n\t\t\t},\n\t\t\tFlags: model.Flags(8),\n\t\t}\n\n\t\tserviceNameQuery := &mocks.Query{}\n\t\tserviceNameQuery.On(\"Bind\", mock.Anything).Return(serviceNameQuery)\n\t\tserviceNameQuery.On(\"Exec\").Return(nil)\n\t\tserviceNameQuery.On(\"String\").Return(\"select from service_name_index\")\n\n\t\tserviceOperationNameQuery := &mocks.Query{}\n\t\tserviceOperationNameQuery.On(\"Bind\", mock.Anything).Return(serviceOperationNameQuery)\n\t\tserviceOperationNameQuery.On(\"Exec\").Return(nil)\n\t\tserviceOperationNameQuery.On(\"String\").Return(\"select from service_operation_index\")\n\n\t\t// Define expected queries\n\t\tw.session.On(\"Query\", serviceNameIndex).Return(serviceNameQuery)\n\t\tw.session.On(\"Query\", serviceOperationIndex).Return(serviceOperationNameQuery)\n\n\t\terr := w.writer.WriteSpan(context.Background(), span)\n\t\trequire.NoError(t, err)\n\t\tw.session.AssertExpectations(t)\n\t\tw.session.AssertNotCalled(t, \"Query\", tagIndex, mock.Anything)\n\t\tw.session.AssertNotCalled(t, \"Query\", durationIndex, mock.Anything)\n\t\tassert.Equal(t, \"planet-express\", *serviceWritten.Load())\n\t\tassert.Equal(t, dbmodel.Operation{\n\t\t\tServiceName:   \"planet-express\",\n\t\t\tSpanKind:      \"\",\n\t\t\tOperationName: \"package-delivery\",\n\t\t}, *operationWritten.Load())\n\t}, StoreIndexesOnly())\n}\n\nfunc TestStorageMode_StoreWithoutIndexing(t *testing.T) {\n\twithSpanWriter(t, 0, func(w *spanWriterTest) {\n\t\tw.writer.serviceNamesWriter = func(_ /* serviceName */ string) error {\n\t\t\tassert.Fail(t, \"Non indexing store shouldn't index\")\n\t\t\treturn nil\n\t\t}\n\t\tspan := &model.Span{\n\t\t\tTraceID: model.NewTraceID(0, 1),\n\t\t\tProcess: &model.Process{\n\t\t\t\tServiceName: \"service-a\",\n\t\t\t},\n\t\t}\n\t\tspanQuery := &mocks.Query{}\n\t\tspanQuery.On(\"Exec\").Return(nil)\n\t\tw.session.On(\"Query\", insertSpan, mock.Anything).Return(spanQuery)\n\n\t\terr := w.writer.WriteSpan(context.Background(), span)\n\n\t\trequire.NoError(t, err)\n\t\tspanQuery.AssertExpectations(t)\n\t\tw.session.AssertExpectations(t)\n\t\tw.session.AssertNotCalled(t, \"Query\", serviceNameIndex, mock.Anything)\n\t}, StoreWithoutIndexing())\n}\n"
  },
  {
    "path": "internal/storage/v1/configurable.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storage\n\nimport (\n\t\"flag\"\n\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n)\n\n// Configurable interface can be implemented by plugins that require external configuration,\n// such as CLI flags, config files, or environment variables.\ntype Configurable interface {\n\t// AddFlags adds CLI flags for configuring this component.\n\tAddFlags(flagSet *flag.FlagSet)\n\n\t// InitFromViper initializes this component with properties from spf13/viper.\n\tInitFromViper(v *viper.Viper, logger *zap.Logger)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/.gitignore",
    "content": "esmapping-generator-*-*\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/README.md",
    "content": "# ElasticSearch Support\n\nThis provides a storage backend for Jaeger using [Elasticsearch](https://www.elastic.co). More information is available on the [Jaeger documentation website](https://www.jaegertracing.io/docs/latest/deployment/#elasticsearch).\n\n## Indices\nIndices will be created depending on the spans timestamp. i.e., a span with\na timestamp on 2017/04/21 will be stored in an index named `jaeger-2017-04-21`.\n\nIt is common to only keep observability data for a limited time.\nHowever, Elasticsearch does not support expiring of old data via TTL.\nTo purge old Jaeger indices, use [jaeger-es-index-cleaner](../../../cmd/es-index-cleaner/).\n\n### Timestamps\nBecause ElasticSearch's `Date` datatype has only millisecond granularity and Jaeger\nrequires microsecond granularity, Jaeger spans' `StartTime` is saved as a long type.\nThe conversion is done automatically.\n\n### Nested fields (tags)\n`Tags` are [nested](https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html) fields in the\nElasticSearch schema used for Jaeger. This allows for better search capabilities and data retention. However, because\nElasticSearch creates a new document for every nested field, there is currently a limit of 50 nested fields per document.\n\n### Shards and Replicas\nNumber of shards and replicas per index can be specified as parameters to the writer and/or through configs under\n`./internal/storage/elasticsearch/config/config.go`. If not specified, it defaults to ElasticSearch defaults: 5 shards and 1 replica.\n[This article](https://www.elastic.co/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster) goes into more information\nabout choosing how many shards should be chosen for optimization.\n\n## Limitations\n\n### Tag query over multiple spans\nThis plugin queries against spans. This means that all tags in a query must lie under the same span for a\nquery to successfully return a trace.\n\n### Case-sensitivity\nQueries are case-sensitive. For example, if a document with service name `ABC` is searched using a query `abc`,\nthe document will not be retrieved.\n\n## Testing\nTo locally test the ElasticSearch storage plugin,\n* have [ElasticSearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html) running on port 9200\n* run `STORAGE=es make storage-integration-test` in the top folder.\n\nAll integration tests also run on pull request via GitHub Actions. This integration test is against ElasticSearch v7.x and v8.x.\n\n* The script used in GitHub Actions can be found under `scripts/e2e/elasticsearch.sh`,\nand that script be run from the top folder to integration test ElasticSearch as well.\nThis script requires Docker to be running.\n\n### Adding tests\nIntegration test framework for storage lie under `../integration`.\nAdd to `../integration/fixtures/traces/*.json` and `../integration/fixtures/queries.json` to add more\ntrace cases.\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/dependencystore/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/dependencystore/storagev1.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n)\n\nvar (\n\t_ dependencystore.Reader = &StoreV1{} // check API conformance\n\t_ dependencystore.Writer = &StoreV1{} // check API conformance\n)\n\ntype StoreV1 struct {\n\tdepStore depstore.CoreDependencyStore\n}\n\n// NewDependencyStoreV1 returns a StoreV1\nfunc NewDependencyStoreV1(p depstore.Params) *StoreV1 {\n\treturn &StoreV1{\n\t\tdepStore: depstore.NewDependencyStore(p),\n\t}\n}\n\n// WriteDependencies implements dependencystore.Writer#WriteDependencies.\nfunc (s *StoreV1) WriteDependencies(ts time.Time, dependencies []model.DependencyLink) error {\n\tdbDependencies := dbmodel.FromDomainDependencies(dependencies)\n\treturn s.depStore.WriteDependencies(ts, dbDependencies)\n}\n\n// CreateTemplates creates index templates.\nfunc (s *StoreV1) CreateTemplates(dependenciesTemplate string) error {\n\treturn s.depStore.CreateTemplates(dependenciesTemplate)\n}\n\n// GetDependencies returns all interservice dependencies\nfunc (s *StoreV1) GetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error) {\n\tdbDependencies, err := s.depStore.GetDependencies(ctx, endTs, lookback)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dbmodel.ToDomainDependencies(dbDependencies), nil\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/dependencystore/storagev1_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dependencystore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/mocks\"\n)\n\nfunc TestV1WriteDependencies(t *testing.T) {\n\tcoreDependencyStore := &mocks.CoreDependencyStore{}\n\tdepStore := StoreV1{depStore: coreDependencyStore}\n\tdependencies := []model.DependencyLink{\n\t\t{\n\t\t\tParent:    \"hello\",\n\t\t\tChild:     \"world\",\n\t\t\tCallCount: 12,\n\t\t},\n\t}\n\tdbDependencies := dbmodel.FromDomainDependencies(dependencies)\n\tts := time.Now()\n\tcoreDependencyStore.On(\"WriteDependencies\", ts, dbDependencies).Return(nil)\n\terr := depStore.WriteDependencies(ts, dependencies)\n\trequire.NoError(t, err)\n}\n\nfunc TestV1CreateTemplates(t *testing.T) {\n\tcoreDependencyStore := &mocks.CoreDependencyStore{}\n\tdepStore := StoreV1{depStore: coreDependencyStore}\n\ttemplateName := \"testing-template\"\n\tcoreDependencyStore.On(\"CreateTemplates\", templateName).Return(nil)\n\terr := depStore.CreateTemplates(templateName)\n\trequire.NoError(t, err)\n}\n\nfunc TestV1GetDependencies(t *testing.T) {\n\ttests := []struct {\n\t\tname                  string\n\t\treturningDependencies []dbmodel.DependencyLink\n\t\treturningErr          error\n\t\texpectedDependencies  []model.DependencyLink\n\t\texpectedErr           string\n\t}{\n\t\t{\n\t\t\tname: \"no error\",\n\t\t\treturningDependencies: []dbmodel.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"hello\",\n\t\t\t\t\tChild:     \"world\",\n\t\t\t\t\tCallCount: 12,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedDependencies: []model.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"hello\",\n\t\t\t\t\tChild:     \"world\",\n\t\t\t\t\tCallCount: 12,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"error\",\n\t\t\treturningErr: errors.New(\"some error\"),\n\t\t\texpectedErr:  \"some error\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcoreDependencyStore := &mocks.CoreDependencyStore{}\n\t\t\tdepStore := StoreV1{depStore: coreDependencyStore}\n\t\t\tts := time.Now()\n\t\t\tdrn := 24 * time.Hour\n\t\t\tcoreDependencyStore.On(\"GetDependencies\", mock.Anything, ts, drn).Return(test.returningDependencies, test.returningErr)\n\t\t\tdeps, err := depStore.GetDependencies(context.Background(), ts, drn)\n\t\t\tif test.expectedErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, test.expectedDependencies, deps)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/factory.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/fswatcher\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/mappings\"\n\tessamplestore \"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/samplingstore\"\n\tesspanstore \"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n\tesdepstorev2 \"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore\"\n)\n\nvar _ io.Closer = (*FactoryBase)(nil)\n\n// FactoryBase for Elasticsearch backend.\ntype FactoryBase struct {\n\tmetricsFactory metrics.Factory\n\tlogger         *zap.Logger\n\ttracer         trace.TracerProvider\n\n\tnewClientFn func(ctx context.Context, c *config.Configuration, logger *zap.Logger, metricsFactory metrics.Factory, httpAuth extensionauth.HTTPClient) (es.Client, error)\n\n\tconfig *config.Configuration\n\n\tclient atomic.Pointer[es.Client]\n\n\tpwdFileWatcher *fswatcher.FSWatcher\n\n\ttemplateBuilder es.TemplateBuilder\n\n\ttags []string\n}\n\nfunc NewFactoryBase(\n\tctx context.Context,\n\tcfg config.Configuration,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n\thttpAuth extensionauth.HTTPClient,\n) (*FactoryBase, error) {\n\tf := &FactoryBase{\n\t\tconfig:      &cfg,\n\t\tnewClientFn: config.NewClient,\n\t\ttracer:      otel.GetTracerProvider(),\n\t}\n\tf.metricsFactory = metricsFactory\n\tf.logger = logger\n\tf.templateBuilder = es.TextTemplateBuilder{}\n\ttags, err := f.config.TagKeysAsFields()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf.tags = tags\n\n\tclient, err := f.newClientFn(ctx, f.config, logger, metricsFactory, httpAuth)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create Elasticsearch client: %w\", err)\n\t}\n\tf.client.Store(&client)\n\n\tif f.config.Authentication.BasicAuthentication.HasValue() {\n\t\tif file := f.config.Authentication.BasicAuthentication.Get().PasswordFilePath; file != \"\" {\n\t\t\twatcher, err := fswatcher.New([]string{file}, f.onPasswordChange, f.logger)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to create watcher for ES client's password: %w\", err)\n\t\t\t}\n\t\t\tf.pwdFileWatcher = watcher\n\t\t}\n\t}\n\n\terr = f.createTemplates(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn f, nil\n}\n\nfunc (f *FactoryBase) getClient() es.Client {\n\tif c := f.client.Load(); c != nil {\n\t\treturn *c\n\t}\n\treturn nil\n}\n\n// GetSpanReaderParams returns the SpanReaderParams which can be used to initialize the v1 and v2 readers.\nfunc (f *FactoryBase) GetSpanReaderParams() esspanstore.SpanReaderParams {\n\treturn esspanstore.SpanReaderParams{\n\t\tClient:              f.getClient,\n\t\tMaxDocCount:         f.config.MaxDocCount,\n\t\tMaxSpanAge:          f.config.MaxSpanAge,\n\t\tIndexPrefix:         f.config.Indices.IndexPrefix,\n\t\tSpanIndex:           f.config.Indices.Spans,\n\t\tServiceIndex:        f.config.Indices.Services,\n\t\tTagDotReplacement:   f.config.Tags.DotReplacement,\n\t\tUseReadWriteAliases: f.config.UseReadWriteAliases,\n\t\tReadAliasSuffix:     f.config.ReadAliasSuffix,\n\t\tRemoteReadClusters:  f.config.RemoteReadClusters,\n\t\tSpanReadAlias:       f.config.SpanReadAlias,\n\t\tServiceReadAlias:    f.config.ServiceReadAlias,\n\t\tLogger:              f.logger,\n\t\tTracer:              f.tracer.Tracer(\"esspanstore.SpanReader\"),\n\t}\n}\n\n// GetSpanWriterParams returns the SpanWriterParams which can be used to initialize the v1 and v2 writers.\nfunc (f *FactoryBase) GetSpanWriterParams() esspanstore.SpanWriterParams {\n\treturn esspanstore.SpanWriterParams{\n\t\tClient:              f.getClient,\n\t\tIndexPrefix:         f.config.Indices.IndexPrefix,\n\t\tSpanIndex:           f.config.Indices.Spans,\n\t\tServiceIndex:        f.config.Indices.Services,\n\t\tAllTagsAsFields:     f.config.Tags.AllAsFields,\n\t\tTagKeysAsFields:     f.tags,\n\t\tTagDotReplacement:   f.config.Tags.DotReplacement,\n\t\tUseReadWriteAliases: f.config.UseReadWriteAliases,\n\t\tWriteAliasSuffix:    f.config.WriteAliasSuffix,\n\t\tSpanWriteAlias:      f.config.SpanWriteAlias,\n\t\tServiceWriteAlias:   f.config.ServiceWriteAlias,\n\t\tLogger:              f.logger,\n\t\tMetricsFactory:      f.metricsFactory,\n\t\tServiceCacheTTL:     f.config.ServiceCacheTTL,\n\t}\n}\n\n// GetDependencyStoreParams returns the esdepstorev2.Params which can be used to initialize the v1 and v2 dependency stores.\nfunc (f *FactoryBase) GetDependencyStoreParams() esdepstorev2.Params {\n\treturn esdepstorev2.Params{\n\t\tClient:              f.getClient,\n\t\tLogger:              f.logger,\n\t\tIndexPrefix:         f.config.Indices.IndexPrefix,\n\t\tIndexDateLayout:     f.config.Indices.Dependencies.DateLayout,\n\t\tMaxDocCount:         f.config.MaxDocCount,\n\t\tUseReadWriteAliases: f.config.UseReadWriteAliases,\n\t}\n}\n\nfunc (f *FactoryBase) CreateSamplingStore(int /* maxBuckets */) (samplingstore.Store, error) {\n\tparams := essamplestore.Params{\n\t\tClient:                 f.getClient,\n\t\tLogger:                 f.logger,\n\t\tIndexPrefix:            f.config.Indices.IndexPrefix,\n\t\tIndexDateLayout:        f.config.Indices.Sampling.DateLayout,\n\t\tIndexRolloverFrequency: config.RolloverFrequencyAsNegativeDuration(f.config.Indices.Sampling.RolloverFrequency),\n\t\tLookback:               f.config.AdaptiveSamplingLookback,\n\t\tMaxDocCount:            f.config.MaxDocCount,\n\t}\n\tstore := essamplestore.NewSamplingStore(params)\n\n\tif f.config.CreateIndexTemplates {\n\t\tmappingBuilder := f.mappingBuilderFromConfig(f.config)\n\t\tsamplingMapping, err := mappingBuilder.GetSamplingMappings()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif _, err := f.getClient().CreateTemplate(params.PrefixedIndexName()).Body(samplingMapping).Do(context.Background()); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create template: %w\", err)\n\t\t}\n\t}\n\n\treturn store, nil\n}\n\nfunc (f *FactoryBase) mappingBuilderFromConfig(cfg *config.Configuration) mappings.MappingBuilder {\n\treturn mappings.MappingBuilder{\n\t\tTemplateBuilder: f.templateBuilder,\n\t\tIndices:         cfg.Indices,\n\t\tEsVersion:       cfg.Version,\n\t\tUseILM:          cfg.UseILM,\n\t}\n}\n\n// Close closes the resources held by the factory\nfunc (f *FactoryBase) Close() error {\n\tvar errs []error\n\n\tif f.pwdFileWatcher != nil {\n\t\terrs = append(errs, f.pwdFileWatcher.Close())\n\t}\n\terrs = append(errs, f.getClient().Close())\n\n\treturn errors.Join(errs...)\n}\n\nfunc (f *FactoryBase) onPasswordChange() {\n\tf.onClientPasswordChange(f.config, &f.client, f.metricsFactory)\n}\n\nfunc (f *FactoryBase) onClientPasswordChange(cfg *config.Configuration, client *atomic.Pointer[es.Client], mf metrics.Factory) {\n\tbasicAuth := cfg.Authentication.BasicAuthentication.Get()\n\tnewPassword, err := loadTokenFromFile(basicAuth.PasswordFilePath)\n\tif err != nil {\n\t\tf.logger.Error(\"failed to reload password for Elasticsearch client\", zap.Error(err))\n\t\treturn\n\t}\n\tf.logger.Sugar().Infof(\"loaded new password of length %d from file\", len(newPassword))\n\tnewCfg := *cfg // copy by value\n\tnewCfg.Authentication.BasicAuthentication = configoptional.Some(config.BasicAuthentication{\n\t\tUsername:         basicAuth.Username,\n\t\tPassword:         newPassword,\n\t\tPasswordFilePath: \"\", // avoid error that both are set\n\t})\n\n\tnewClient, err := f.newClientFn(context.Background(), &newCfg, f.logger, mf, nil)\n\tif err != nil {\n\t\tf.logger.Error(\"failed to recreate Elasticsearch client with new password\", zap.Error(err))\n\t\treturn\n\t}\n\tif oldClient := *client.Swap(&newClient); oldClient != nil {\n\t\tif err := oldClient.Close(); err != nil {\n\t\t\tf.logger.Error(\"failed to close Elasticsearch client\", zap.Error(err))\n\t\t}\n\t}\n}\n\nfunc (f *FactoryBase) Purge(ctx context.Context) error {\n\tesClient := f.getClient()\n\t_, err := esClient.DeleteIndex(\"*\").Do(ctx)\n\treturn err\n}\n\nfunc loadTokenFromFile(path string) (string, error) {\n\tb, err := os.ReadFile(filepath.Clean(path))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn strings.TrimRight(string(b), \"\\r\\n\"), nil\n}\n\nfunc (f *FactoryBase) createTemplates(ctx context.Context) error {\n\tif f.config.CreateIndexTemplates {\n\t\tmappingBuilder := f.mappingBuilderFromConfig(f.config)\n\t\tspanMapping, serviceMapping, err := mappingBuilder.GetSpanServiceMappings()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tjaegerSpanIdx := f.config.Indices.IndexPrefix.Apply(\"jaeger-span\")\n\t\tjaegerServiceIdx := f.config.Indices.IndexPrefix.Apply(\"jaeger-service\")\n\t\t_, err = f.getClient().CreateTemplate(jaegerSpanIdx).Body(spanMapping).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create template %q: %w\", jaegerSpanIdx, err)\n\t\t}\n\t\t_, err = f.getClient().CreateTemplate(jaegerServiceIdx).Body(serviceMapping).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create template %q: %w\", jaegerServiceIdx, err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/factory_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\t\"go.opentelemetry.io/otel\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n\tesdepstorev2 \"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nvar mockEsServerResponse = []byte(`\n{\n\t\"Version\": {\n\t\t\"Number\": \"6\"\n\t}\n}\n`)\n\nfunc TestElasticsearchFactoryBase(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tt.Cleanup(server.Close)\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{server.URL},\n\t\tLogLevel: \"debug\",\n\t}\n\tf, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zaptest.NewLogger(t), nil)\n\trequire.NoError(t, err)\n\treaderParams := f.GetSpanReaderParams()\n\tassert.IsType(t, spanstore.SpanReaderParams{}, readerParams)\n\twriterParams := f.GetSpanWriterParams()\n\tassert.IsType(t, spanstore.SpanWriterParams{}, writerParams)\n\tdepParams := f.GetDependencyStoreParams()\n\tassert.IsType(t, esdepstorev2.Params{}, depParams)\n\t_, err = f.CreateSamplingStore(1)\n\trequire.NoError(t, err)\n\trequire.NoError(t, f.Close())\n}\n\nfunc TestFactoryBase_Purge(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tsetupMock   func(*mocks.IndicesDeleteService)\n\t\texpectedErr bool\n\t}{\n\t\t{\n\t\t\tname: \"successful purge\",\n\t\t\tsetupMock: func(mockDelete *mocks.IndicesDeleteService) {\n\t\t\t\tmockDelete.On(\"Do\", mock.Anything).Return(nil, nil)\n\t\t\t},\n\t\t\texpectedErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"purge error\",\n\t\t\tsetupMock: func(mockDelete *mocks.IndicesDeleteService) {\n\t\t\t\tmockDelete.On(\"Do\", mock.Anything).Return(nil, errors.New(\"delete error\"))\n\t\t\t},\n\t\t\texpectedErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Create a real factory with a mock ES client\n\t\t\tmockClient := &mocks.Client{}\n\t\t\tmockDelete := &mocks.IndicesDeleteService{}\n\t\t\tmockClient.On(\"DeleteIndex\", \"*\").Return(mockDelete)\n\n\t\t\ttt.setupMock(mockDelete)\n\n\t\t\t// Create a mock client that will be stored in the atomic.Pointer\n\t\t\tf := &FactoryBase{\n\t\t\t\tclient: atomic.Pointer[es.Client]{},\n\t\t\t}\n\t\t\t// Create a concrete type that implements es.Client\n\t\t\tvar client es.Client = mockClient\n\t\t\t// Store the client in the atomic.Pointer\n\t\t\tf.client.Store(&client)\n\n\t\t\terr := f.Purge(context.Background())\n\t\t\tif tt.expectedErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t// Verify the mock was called as expected\n\t\t\tmockClient.AssertExpectations(t)\n\t\t\tmockDelete.AssertExpectations(t)\n\t\t})\n\t}\n}\n\nfunc TestElasticsearchTagsFileDoNotExist(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tt.Cleanup(server.Close)\n\tcfg := escfg.Configuration{\n\t\tServers: []string{server.URL},\n\t\tTags: escfg.TagsAsFields{\n\t\t\tFile: \"fixtures/file-does-not-exist.txt\",\n\t\t},\n\t\tLogLevel: \"debug\",\n\t}\n\tf, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zaptest.NewLogger(t), nil)\n\trequire.ErrorContains(t, err, \"open fixtures/file-does-not-exist.txt: no such file or directory\")\n\tassert.Nil(t, f)\n}\n\nfunc TestTagKeysAsFields(t *testing.T) {\n\ttests := []struct {\n\t\tpath          string\n\t\tinclude       string\n\t\texpected      []string\n\t\terrorExpected bool\n\t}{\n\t\t{\n\t\t\tpath:          \"fixtures/do_not_exists.txt\",\n\t\t\terrorExpected: true,\n\t\t},\n\t\t{\n\t\t\tpath:     \"fixtures/tags_01.txt\",\n\t\t\texpected: []string{\"foo\", \"bar\", \"space\"},\n\t\t},\n\t\t{\n\t\t\tpath:     \"fixtures/tags_02.txt\",\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tinclude:  \"televators,eriatarka,thewidow\",\n\t\t\texpected: []string{\"televators\", \"eriatarka\", \"thewidow\"},\n\t\t},\n\t\t{\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tpath:     \"fixtures/tags_01.txt\",\n\t\t\tinclude:  \"televators,eriatarka,thewidow\",\n\t\t\texpected: []string{\"foo\", \"bar\", \"space\", \"televators\", \"eriatarka\", \"thewidow\"},\n\t\t},\n\t\t{\n\t\t\tpath:     \"fixtures/tags_02.txt\",\n\t\t\tinclude:  \"televators,eriatarka,thewidow\",\n\t\t\texpected: []string{\"televators\", \"eriatarka\", \"thewidow\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tcfg := escfg.Configuration{\n\t\t\tTags: escfg.TagsAsFields{\n\t\t\t\tFile:    test.path,\n\t\t\t\tInclude: test.include,\n\t\t\t},\n\t\t}\n\n\t\ttags, err := cfg.TagKeysAsFields()\n\t\tif test.errorExpected {\n\t\t\trequire.Error(t, err)\n\t\t\tassert.Nil(t, tags)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, test.expected, tags)\n\t\t}\n\t}\n}\n\nfunc TestCreateTemplates(t *testing.T) {\n\ttests := []struct {\n\t\terr                    string\n\t\tspanTemplateService    func() *mocks.TemplateCreateService\n\t\tserviceTemplateService func() *mocks.TemplateCreateService\n\t\tindexPrefix            escfg.IndexPrefix\n\t}{\n\t\t{\n\t\t\tspanTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := &mocks.TemplateCreateService{}\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, nil)\n\t\t\t\treturn tService\n\t\t\t},\n\t\t\tserviceTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := &mocks.TemplateCreateService{}\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, nil)\n\t\t\t\treturn tService\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tspanTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := &mocks.TemplateCreateService{}\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, nil)\n\t\t\t\treturn tService\n\t\t\t},\n\t\t\tserviceTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := &mocks.TemplateCreateService{}\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, nil)\n\t\t\t\treturn tService\n\t\t\t},\n\t\t\tindexPrefix: \"test\",\n\t\t},\n\t\t{\n\t\t\terr: \"span-template-error\",\n\t\t\tspanTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := new(mocks.TemplateCreateService)\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, errors.New(\"span-template-error\"))\n\t\t\t\treturn tService\n\t\t\t},\n\t\t\tserviceTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := new(mocks.TemplateCreateService)\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, nil)\n\t\t\t\treturn tService\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: \"service-template-error\",\n\t\t\tspanTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := new(mocks.TemplateCreateService)\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, nil)\n\t\t\t\treturn tService\n\t\t\t},\n\t\t\tserviceTemplateService: func() *mocks.TemplateCreateService {\n\t\t\t\ttService := new(mocks.TemplateCreateService)\n\t\t\t\ttService.On(\"Body\", mock.Anything).Return(tService)\n\t\t\t\ttService.On(\"Do\", context.Background()).Return(nil, errors.New(\"service-template-error\"))\n\t\t\t\treturn tService\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tf := FactoryBase{}\n\t\tmockClient := &mocks.Client{}\n\t\tf.newClientFn = func(_ context.Context, _ *escfg.Configuration, _ *zap.Logger, _ metrics.Factory, _ extensionauth.HTTPClient) (es.Client, error) {\n\t\t\treturn mockClient, nil\n\t\t}\n\t\tf.logger = zaptest.NewLogger(t)\n\t\tf.metricsFactory = metrics.NullFactory\n\t\tf.config = &escfg.Configuration{CreateIndexTemplates: true, Indices: escfg.Indices{\n\t\t\tIndexPrefix: test.indexPrefix,\n\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\tShards:   3,\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tPriority: 10,\n\t\t\t},\n\t\t\tServices: escfg.IndexOptions{\n\t\t\t\tShards:   3,\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tPriority: 10,\n\t\t\t},\n\t\t}}\n\t\tf.tracer = otel.GetTracerProvider()\n\t\tclient, err := f.newClientFn(context.Background(), &escfg.Configuration{}, zaptest.NewLogger(t), metrics.NullFactory, nil)\n\t\trequire.NoError(t, err)\n\t\tf.client.Store(&client)\n\t\tf.templateBuilder = es.TextTemplateBuilder{}\n\t\tjaegerSpanId := test.indexPrefix.Apply(\"jaeger-span\")\n\t\tjaegerServiceId := test.indexPrefix.Apply(\"jaeger-service\")\n\t\tmockClient.On(\"CreateTemplate\", jaegerSpanId).Return(test.spanTemplateService())\n\t\tmockClient.On(\"CreateTemplate\", jaegerServiceId).Return(test.serviceTemplateService())\n\t\terr = f.createTemplates(context.Background())\n\t\tif test.err != \"\" {\n\t\t\trequire.ErrorContains(t, err, test.err)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}\n}\n\nfunc TestESStorageFactoryWithConfig(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tdefer server.Close()\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{server.URL},\n\t\tLogLevel: \"error\",\n\t}\n\tfactory, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zap.NewNop(), nil)\n\trequire.NoError(t, err)\n\tfactory.Close()\n}\n\nfunc TestESStorageFactoryWithConfigError(t *testing.T) {\n\tt.Parallel()\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/\" {\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}))\n\tdefer server.Close()\n\tcfg := escfg.Configuration{\n\t\tServers:            []string{server.URL},\n\t\tDisableHealthCheck: true,\n\t\tLogLevel:           \"error\",\n\t}\n\t_, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zap.NewNop(), nil)\n\trequire.ErrorContains(t, err, \"failed to create Elasticsearch client\")\n}\n\nfunc TestPasswordFromFile(t *testing.T) {\n\tt.Cleanup(func() {\n\t\ttestutils.VerifyGoLeaksOnce(t)\n\t})\n\tt.Run(\"primary client\", func(t *testing.T) {\n\t\trunPasswordFromFileTest(t)\n\t})\n\n\tt.Run(\"load token error\", func(t *testing.T) {\n\t\tfile := filepath.Join(t.TempDir(), \"does not exist\")\n\t\ttoken, err := loadTokenFromFile(file)\n\t\trequire.Error(t, err)\n\t\tassert.Empty(t, token)\n\t})\n}\n\nfunc runPasswordFromFileTest(t *testing.T) {\n\tconst (\n\t\tpwd1 = \"first password\"\n\t\tpwd2 = \"second password\"\n\t\t// and with user name\n\t\tupwd1 = \"user:\" + pwd1\n\t\tupwd2 = \"user:\" + pwd2\n\t)\n\tvar authReceived sync.Map\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tt.Logf(\"request to fake ES server: %v\", r)\n\t\t// epecting header in the form Authorization:[Basic OmZpcnN0IHBhc3N3b3Jk]\n\t\th := strings.Split(r.Header.Get(\"Authorization\"), \" \")\n\t\tif !assert.Len(t, h, 2) {\n\t\t\treturn\n\t\t}\n\t\tassert.Equal(t, \"Basic\", h[0])\n\t\tauthBytes, err := base64.StdEncoding.DecodeString(h[1])\n\t\tassert.NoError(t, err, \"header: %s\", h)\n\t\tauth := string(authBytes)\n\t\tauthReceived.Store(auth, auth)\n\t\tt.Logf(\"request to fake ES server contained auth=%s\", auth)\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tt.Cleanup(server.Close)\n\n\tpwdFile := filepath.Join(t.TempDir(), \"pwd\")\n\trequire.NoError(t, os.WriteFile(pwdFile, []byte(pwd1), 0o600))\n\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{server.URL},\n\t\tLogLevel: \"debug\",\n\t\tAuthentication: escfg.Authentication{\n\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\tUsername:         \"user\",\n\t\t\t\tPasswordFilePath: pwdFile,\n\t\t\t}),\n\t\t},\n\t\tBulkProcessing: escfg.BulkProcessing{\n\t\t\tMaxBytes:   -1, // disable bulk\n\t\t\tMaxActions: -1, // disable bulk; the test only validates auth headers\n\t\t},\n\t}\n\tf, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zap.NewNop(), nil)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\trequire.NoError(t, f.Close())\n\t})\n\n\twriter := spanstore.NewSpanWriter(f.GetSpanWriterParams())\n\tspan1 := &dbmodel.Span{\n\t\tProcess: dbmodel.Process{ServiceName: \"foo\"},\n\t}\n\twriter.WriteSpan(time.Now(), span1)\n\tassert.Eventually(t,\n\t\tfunc() bool {\n\t\t\tpwd, ok := authReceived.Load(upwd1)\n\t\t\treturn ok && pwd == upwd1\n\t\t},\n\t\t5*time.Second, time.Millisecond,\n\t\t\"expecting es.Client to send the first password\",\n\t)\n\n\tt.Log(\"replace password in the file\")\n\tclient1 := f.getClient()\n\tnewPwdFile := filepath.Join(t.TempDir(), \"pwd2\")\n\trequire.NoError(t, os.WriteFile(newPwdFile, []byte(pwd2), 0o600))\n\trequire.NoError(t, os.Rename(newPwdFile, pwdFile))\n\n\tassert.Eventually(t,\n\t\tfunc() bool {\n\t\t\tclient2 := f.getClient()\n\t\t\treturn client1 != client2\n\t\t},\n\t\t5*time.Second, time.Millisecond,\n\t\t\"expecting es.Client to change for the new password\",\n\t)\n\n\tspan2 := &dbmodel.Span{\n\t\tProcess: dbmodel.Process{ServiceName: \"foo\"},\n\t}\n\twriter.WriteSpan(time.Now(), span2)\n\tassert.Eventually(t,\n\t\tfunc() bool {\n\t\t\tpwd, ok := authReceived.Load(upwd2)\n\t\t\treturn ok && pwd == upwd2\n\t\t},\n\t\t5*time.Second, time.Millisecond,\n\t\t\"expecting es.Client to send the new password\",\n\t)\n}\n\nfunc TestFactoryESClientsAreNil(t *testing.T) {\n\tf := &FactoryBase{}\n\tassert.Nil(t, f.getClient())\n}\n\nfunc TestPasswordFromFileErrors(t *testing.T) {\n\tdefer testutils.VerifyGoLeaksOnce(t)\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tdefer server.Close()\n\n\tpwdFile := filepath.Join(t.TempDir(), \"pwd\")\n\trequire.NoError(t, os.WriteFile(pwdFile, []byte(\"first password\"), 0o600))\n\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{server.URL},\n\t\tLogLevel: \"debug\",\n\t\tAuthentication: escfg.Authentication{\n\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\tPasswordFilePath: pwdFile,\n\t\t\t}),\n\t\t},\n\t\tBulkProcessing: escfg.BulkProcessing{\n\t\t\tMaxBytes:   -1, // disable bulk\n\t\t\tMaxActions: -1, // disable bulk; the test only validates error paths\n\t\t},\n\t}\n\n\tlogger, buf := testutils.NewEchoLogger(t)\n\tf, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, logger, nil)\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\n\tf.config.Servers = []string{}\n\tf.onPasswordChange()\n\tassert.Contains(t, buf.String(), \"no servers specified\")\n\n\trequire.NoError(t, os.Remove(pwdFile))\n\tf.onPasswordChange()\n}\n\nfunc TestFactoryBase_NewClient_WatcherError(t *testing.T) {\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{\"http://localhost:9200\"},\n\t\tLogLevel: \"debug\",\n\t\tAuthentication: escfg.Authentication{\n\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\tUsername:         \"testuser\",\n\t\t\t\tPasswordFilePath: \"/nonexistent/path/to/password.txt\",\n\t\t\t}),\n\t\t},\n\t}\n\n\t_, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zaptest.NewLogger(t), nil)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"failed to initialize basic authentication\")\n\tassert.Contains(t, err.Error(), \"failed to get token from file\")\n}\n\nfunc TestElasticsearchFactoryBaseWithAuthenticator(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tt.Cleanup(server.Close)\n\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{server.URL},\n\t\tLogLevel: \"debug\",\n\t\tBulkProcessing: escfg.BulkProcessing{\n\t\t\tMaxBytes:   -1, // disable bulk\n\t\t\tMaxActions: -1, // disable bulk; the test only validates authenticator setup\n\t\t},\n\t}\n\n\t// Mock authenticator\n\tmockAuth := &mockHTTPAuthenticator{}\n\n\tf, err := NewFactoryBase(context.Background(), cfg, metrics.NullFactory, zaptest.NewLogger(t), mockAuth)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, f)\n\tdefer require.NoError(t, f.Close())\n\n\t// Verify factory is properly initialized with authenticator\n\treaderParams := f.GetSpanReaderParams()\n\tassert.IsType(t, spanstore.SpanReaderParams{}, readerParams)\n}\n\n// mockHTTPAuthenticator implements extensionauth.HTTPClient for testing\ntype mockHTTPAuthenticator struct{}\n\nfunc (*mockHTTPAuthenticator) RoundTripper(base http.RoundTripper) (http.RoundTripper, error) {\n\treturn &mockRoundTripper{base: base}, nil\n}\n\n// mockRoundTripper wraps the base RoundTripper\ntype mockRoundTripper struct {\n\tbase http.RoundTripper\n}\n\nfunc (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Set(\"Authorization\", \"Bearer mock-token\")\n\tif m.base != nil {\n\t\treturn m.base.RoundTrip(req)\n\t}\n\treturn &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/fixtures/tags_01.txt",
    "content": "foo\nbar\n      space     \n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/fixtures/tags_02.txt",
    "content": "\n\n\n\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/command.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage mappings\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n)\n\nfunc Command() *cobra.Command {\n\toptions := Options{}\n\tcommand := &cobra.Command{\n\t\tUse:   \"elasticsearch-mappings\",\n\t\tShort: \"Jaeger esmapping-generator prints rendered mappings as string\",\n\t\tLong:  \"Jaeger esmapping-generator renders passed templates with provided values and prints rendered output to stdout\",\n\t\tRunE: func(_ *cobra.Command, _ /* args */ []string) error {\n\t\t\tresult, err := generateMappings(options)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error generating mappings: %w\", err)\n\t\t\t}\n\t\t\tfmt.Println(result)\n\t\t\treturn nil\n\t\t},\n\t}\n\toptions.AddFlags(command)\n\n\treturn command\n}\n\nfunc generateMappings(options Options) (string, error) {\n\tif _, err := MappingTypeFromString(options.Mapping); err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid mapping type '%s': please pass either 'jaeger-service' or 'jaeger-span' as the mapping type %w\", options.Mapping, err)\n\t}\n\n\tparsedMapping, err := getMappingAsString(es.TextTemplateBuilder{}, options)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to render mapping to string: %w\", err)\n\t}\n\n\treturn parsedMapping, nil\n}\n\n// getMappingAsString returns rendered index templates as string\nfunc getMappingAsString(builder es.TemplateBuilder, opt Options) (string, error) {\n\tenableILM, err := strconv.ParseBool(opt.UseILM)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tindexOpts := config.IndexOptions{\n\t\tShards:   opt.Shards,\n\t\tReplicas: opt.Replicas,\n\t}\n\tmappingBuilder := MappingBuilder{\n\t\tTemplateBuilder: builder,\n\t\tIndices: config.Indices{\n\t\t\tIndexPrefix:  config.IndexPrefix(opt.IndexPrefix),\n\t\t\tSpans:        indexOpts,\n\t\t\tServices:     indexOpts,\n\t\t\tDependencies: indexOpts,\n\t\t\tSampling:     indexOpts,\n\t\t},\n\t\tEsVersion:     opt.EsVersion,\n\t\tUseILM:        enableILM,\n\t\tILMPolicyName: opt.ILMPolicyName,\n\t}\n\n\tmappingType, err := MappingTypeFromString(opt.Mapping)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn mappingBuilder.GetMapping(mappingType)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/command_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage mappings\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n)\n\nfunc TestCommandExecute(t *testing.T) {\n\tcmd := Command()\n\n\t// TempFile to capture output\n\ttempFile, err := os.Create(t.TempDir() + \"command-output-*.txt\")\n\trequire.NoError(t, err)\n\n\t// Redirect stdout to the TempFile\n\toldStdout := os.Stdout\n\tos.Stdout = tempFile\n\tdefer func() { os.Stdout = oldStdout }()\n\n\terr = cmd.ParseFlags([]string{\n\t\t\"--mapping=jaeger-span\",\n\t\t\"--es-version=7\",\n\t\t\"--shards=5\",\n\t\t\"--replicas=1\",\n\t\t\"--index-prefix=jaeger-index\",\n\t\t\"--use-ilm=false\",\n\t\t\"--ilm-policy-name=jaeger-ilm-policy\",\n\t})\n\trequire.NoError(t, err)\n\trequire.NoError(t, cmd.Execute())\n\n\toutput, err := os.ReadFile(tempFile.Name())\n\trequire.NoError(t, err)\n\n\tvar jsonOutput map[string]any\n\terr = json.Unmarshal(output, &jsonOutput)\n\trequire.NoError(t, err, \"Output should be valid JSON\")\n}\n\nfunc TestCommandExecuteError(t *testing.T) {\n\tcmd := Command()\n\trequire.NoError(t, cmd.ParseFlags([]string{\"--mapping=foobar\"}))\n\trequire.ErrorContains(t, cmd.Execute(), \"foobar\")\n}\n\nfunc TestIsValidOption(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\targ           string\n\t\texpectedValue bool\n\t}{\n\t\t{name: \"span mapping\", arg: \"jaeger-span\", expectedValue: true},\n\t\t{name: \"service mapping\", arg: \"jaeger-service\", expectedValue: true},\n\t\t{name: \"Invalid mapping\", arg: \"dependency-service\", expectedValue: false},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := MappingTypeFromString(test.arg)\n\t\t\tif test.expectedValue {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.Error(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_getMappingAsString(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\targs    Options\n\t\twant    string\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname: \"ES version 7\", args: Options{Mapping: \"jaeger-span\", EsVersion: 7, Shards: 5, Replicas: new(int64(1)), IndexPrefix: \"test\", UseILM: \"true\", ILMPolicyName: \"jaeger-test-policy\"},\n\t\t\twant: \"ES version 7\",\n\t\t},\n\t\t{\n\t\t\tname: \"Parse Error version 7\", args: Options{Mapping: \"jaeger-span\", EsVersion: 7, Shards: 5, Replicas: new(int64(1)), IndexPrefix: \"test\", UseILM: \"true\", ILMPolicyName: \"jaeger-test-policy\"},\n\t\t\twantErr: errors.New(\"parse error\"),\n\t\t},\n\t\t{\n\t\t\tname: \"Parse bool error\", args: Options{Mapping: \"jaeger-span\", EsVersion: 7, Shards: 5, Replicas: new(int64(1)), IndexPrefix: \"test\", UseILM: \"foo\", ILMPolicyName: \"jaeger-test-policy\"},\n\t\t\twantErr: errors.New(\"strconv.ParseBool: parsing \\\"foo\\\": invalid syntax\"),\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Mapping type\", args: Options{Mapping: \"invalid-mapping\", EsVersion: 7, Shards: 5, Replicas: new(int64(1)), IndexPrefix: \"test\", UseILM: \"true\", ILMPolicyName: \"jaeger-test-policy\"},\n\t\t\twantErr: errors.New(\"invalid mapping type: invalid-mapping\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Prepare\n\t\t\tmockTemplateApplier := &mocks.TemplateApplier{}\n\t\t\tmockTemplateApplier.On(\"Execute\", mock.Anything, mock.Anything).Return(\n\t\t\t\tfunc(wr io.Writer, _ any) error {\n\t\t\t\t\twr.Write([]byte(tt.want))\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t)\n\t\t\tmockTemplateBuilder := &mocks.TemplateBuilder{}\n\t\t\tmockTemplateBuilder.On(\"Parse\", mock.Anything).Return(mockTemplateApplier, tt.wantErr)\n\n\t\t\t// Test\n\t\t\tgot, err := getMappingAsString(mockTemplateBuilder, tt.args)\n\n\t\t\t// Validate\n\t\t\tif tt.wantErr != nil {\n\t\t\t\trequire.EqualError(t, err, tt.wantErr.Error())\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc TestGenerateMappings(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\toptions   Options\n\t\texpectErr string\n\t}{\n\t\t{\n\t\t\tname: \"bad ILM setting\",\n\t\t\toptions: Options{\n\t\t\t\tMapping: \"jaeger-span\",\n\t\t\t\tUseILM:  \"foobar\",\n\t\t\t},\n\t\t\texpectErr: \"foobar\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid jaeger-span mapping\",\n\t\t\toptions: Options{\n\t\t\t\tMapping:       \"jaeger-span\",\n\t\t\t\tEsVersion:     7,\n\t\t\t\tShards:        5,\n\t\t\t\tReplicas:      new(int64(1)),\n\t\t\t\tIndexPrefix:   \"jaeger-index\",\n\t\t\t\tUseILM:        \"false\",\n\t\t\t\tILMPolicyName: \"jaeger-ilm-policy\",\n\t\t\t},\n\t\t\texpectErr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid jaeger-service mapping\",\n\t\t\toptions: Options{\n\t\t\t\tMapping:       \"jaeger-service\",\n\t\t\t\tEsVersion:     7,\n\t\t\t\tShards:        5,\n\t\t\t\tReplicas:      new(int64(1)),\n\t\t\t\tIndexPrefix:   \"jaeger-service-index\",\n\t\t\t\tUseILM:        \"true\",\n\t\t\t\tILMPolicyName: \"service-ilm-policy\",\n\t\t\t},\n\t\t\texpectErr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"invalid mapping type\",\n\t\t\toptions: Options{\n\t\t\t\tMapping: \"invalid-mapping\",\n\t\t\t},\n\t\t\texpectErr: \"invalid-mapping\",\n\t\t},\n\t\t{\n\t\t\tname: \"missing mapping flag\",\n\t\t\toptions: Options{\n\t\t\t\tMapping: \"\",\n\t\t\t},\n\t\t\texpectErr: \"invalid mapping type ''\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult, err := generateMappings(tt.options)\n\t\t\tif tt.expectErr != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err, \"Did not expect an error\")\n\n\t\t\t\tvar parsed map[string]any\n\t\t\t\terr = json.Unmarshal([]byte(result), &parsed)\n\t\t\t\trequire.NoError(t, err, \"Expected valid JSON output\")\n\n\t\t\t\tassert.NotEmpty(t, parsed[\"index_patterns\"], \"Expected index_patterns to be present\")\n\t\t\t\tassert.NotEmpty(t, parsed[\"mappings\"], \"Expected mappings to be present\")\n\t\t\t\tassert.NotEmpty(t, parsed[\"settings\"], \"Expected settings to be present\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-dependencies-6.json",
    "content": "{\n  \"template\": \"*jaeger-dependencies-*\",\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-dependencies-7.json",
    "content": "{\n  \"index_patterns\": \"*jaeger-dependencies-*\",\n  \"aliases\": {\n    \"test-jaeger-dependencies-read\" : {}\n  },\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n    ,\"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-dependencies-write\"\n    }\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-dependencies-8.json",
    "content": "{\n  \"priority\": 502,\n  \"index_patterns\": \"test-jaeger-dependencies-*\",\n  \"template\": {\n    \"aliases\": {\n      \"test-jaeger-dependencies-read\": {}\n    },\n    \"settings\": {\n      \"index.number_of_shards\": 3,\n      \"index.number_of_replicas\": 3,\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true,\n      \"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-dependencies-write\"\n      }\n    },\n    \"mappings\": {}\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-sampling-6.json",
    "content": "{\n  \"template\": \"*jaeger-sampling-*\",\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-sampling-7.json",
    "content": "{\n  \"index_patterns\": \"*jaeger-sampling-*\",\n  \"aliases\": {\n    \"test-jaeger-sampling-read\" : {}\n  },\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n    ,\"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-sampling-write\"\n    }\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-sampling-8.json",
    "content": "{\n  \"priority\": 503,\n  \"index_patterns\": \"test-jaeger-sampling-*\",\n  \"template\": {\n    \"aliases\": {\n      \"test-jaeger-sampling-read\": {}\n    },\n    \"settings\": {\n      \"index.number_of_shards\": 3,\n      \"index.number_of_replicas\": 3,\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true,\n      \"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-sampling-write\"\n      }\n    },\n    \"mappings\": {}\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-service-6.json",
    "content": "{\n  \"template\": \"*jaeger-service-*\",\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true,\n    \"index.mapper.dynamic\":false\n  },\n  \"mappings\":{\n    \"_default_\":{\n      \"_all\":{\n        \"enabled\":false\n      },\n      \"dynamic_templates\":[\n        {\n          \"span_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"process.tag.*\"\n          }\n        }\n      ]\n    },\n    \"service\":{\n      \"properties\":{\n        \"serviceName\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"operationName\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-service-7.json",
    "content": "{\n  \"index_patterns\": \"*test-jaeger-service-*\",\n  \"aliases\": {\n    \"test-jaeger-service-read\" : {}\n  },\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n    ,\"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-service-write\"\n    }\n  },\n  \"mappings\":{\n    \"dynamic_templates\":[\n      {\n        \"span_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"tag.*\"\n        }\n      },\n      {\n        \"process_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"process.tag.*\"\n        }\n      }\n    ],\n    \"properties\":{\n      \"serviceName\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"operationName\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-service-8.json",
    "content": "{\n  \"priority\": 501,\n  \"index_patterns\": \"test-jaeger-service-*\",\n  \"template\": {\n    \"aliases\": {\n      \"test-jaeger-service-read\": {}\n    },\n    \"settings\": {\n      \"index.number_of_shards\": 3,\n      \"index.number_of_replicas\": 3,\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true,\n      \"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-service-write\"\n      }\n    },\n    \"mappings\": {\n      \"dynamic_templates\": [\n        {\n          \"span_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"process.tag.*\"\n          }\n        }\n      ],\n      \"properties\": {\n        \"serviceName\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"operationName\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-span-6.json",
    "content": "{\n  \"template\": \"*jaeger-span-*\",\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true,\n    \"index.mapper.dynamic\":false\n  },\n  \"mappings\":{\n    \"_default_\":{\n      \"_all\":{\n        \"enabled\":false\n      },\n      \"dynamic_templates\":[\n        {\n          \"span_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"process.tag.*\"\n          }\n        }\n      ]\n    },\n    \"span\":{\n      \"properties\":{\n        \"traceID\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"parentSpanID\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"spanID\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"operationName\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"startTime\":{\n          \"type\":\"long\"\n        },\n        \"startTimeMillis\":{\n          \"type\":\"date\",\n          \"format\":\"epoch_millis\"\n        },\n        \"duration\":{\n          \"type\":\"long\"\n        },\n        \"flags\":{\n          \"type\":\"integer\"\n        },\n        \"logs\":{\n          \"type\":\"nested\",\n          \"dynamic\":false,\n          \"properties\":{\n            \"timestamp\":{\n              \"type\":\"long\"\n            },\n            \"fields\":{\n              \"type\":\"nested\",\n              \"dynamic\":false,\n              \"properties\":{\n                \"key\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"value\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"type\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                }\n              }\n            }\n          }\n        },\n        \"process\":{\n          \"properties\":{\n            \"serviceName\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"tag\":{\n              \"type\":\"object\"\n            },\n            \"tags\":{\n              \"type\":\"nested\",\n              \"dynamic\":false,\n              \"properties\":{\n                \"key\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"value\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"type\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                }\n              }\n            }\n          }\n        },\n        \"references\":{\n          \"type\":\"nested\",\n          \"dynamic\":false,\n          \"properties\":{\n            \"refType\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"traceID\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"spanID\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            }\n          }\n        },\n        \"tag\":{\n          \"type\":\"object\"\n        },\n        \"tags\":{\n          \"type\":\"nested\",\n          \"dynamic\":false,\n          \"properties\":{\n            \"key\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"value\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"type\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-span-7.json",
    "content": "{\n  \"index_patterns\": \"*test-jaeger-span-*\",\n  \"aliases\": {\n    \"test-jaeger-span-read\": {}\n  },\n  \"settings\":{\n    \"index.number_of_shards\": 3,\n    \"index.number_of_replicas\": 3,\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n    ,\"lifecycle\": {\n      \"name\": \"jaeger-test-policy\",\n      \"rollover_alias\": \"test-jaeger-span-write\"\n    }\n  },\n  \"mappings\":{\n    \"dynamic_templates\":[\n      {\n        \"span_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"tag.*\"\n        }\n      },\n      {\n        \"process_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"process.tag.*\"\n        }\n      }\n    ],\n    \"properties\":{\n      \"traceID\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"parentSpanID\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"spanID\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"operationName\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"startTime\":{\n        \"type\":\"long\"\n      },\n      \"startTimeMillis\":{\n        \"type\":\"date\",\n        \"format\":\"epoch_millis\"\n      },\n      \"duration\":{\n        \"type\":\"long\"\n      },\n      \"flags\":{\n        \"type\":\"integer\"\n      },\n      \"logs\":{\n        \"type\":\"nested\",\n        \"dynamic\":false,\n        \"properties\":{\n          \"timestamp\":{\n            \"type\":\"long\"\n          },\n          \"fields\":{\n            \"type\":\"nested\",\n            \"dynamic\":false,\n            \"properties\":{\n              \"key\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"value\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"type\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              }\n            }\n          }\n        }\n      },\n      \"process\":{\n        \"properties\":{\n          \"serviceName\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"tag\":{\n            \"type\":\"object\"\n          },\n          \"tags\":{\n            \"type\":\"nested\",\n            \"dynamic\":false,\n            \"properties\":{\n              \"key\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"value\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"type\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              }\n            }\n          }\n        }\n      },\n      \"references\":{\n        \"type\":\"nested\",\n        \"dynamic\":false,\n        \"properties\":{\n          \"refType\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"traceID\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"spanID\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          }\n        }\n      },\n      \"tag\":{\n        \"type\":\"object\"\n      },\n      \"tags\":{\n        \"type\":\"nested\",\n        \"dynamic\":false,\n        \"properties\":{\n          \"key\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"value\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"type\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/fixtures/jaeger-span-8.json",
    "content": "{\n  \"priority\": 500,\n  \"index_patterns\": \"test-jaeger-span-*\",\n  \"template\": {\n    \"aliases\": {\n      \"test-jaeger-span-read\": {}\n    },\n    \"settings\": {\n      \"index.number_of_shards\": 3,\n      \"index.number_of_replicas\": 3,\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true,\n      \"lifecycle\": {\n        \"name\": \"jaeger-test-policy\",\n        \"rollover_alias\": \"test-jaeger-span-write\"\n      }\n    },\n    \"mappings\": {\n      \"dynamic_templates\": [\n        {\n          \"span_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"process.tag.*\"\n          }\n        }\n      ],\n      \"properties\": {\n        \"traceID\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"parentSpanID\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"spanID\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"operationName\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"startTime\": {\n          \"type\": \"long\"\n        },\n        \"startTimeMillis\": {\n          \"type\": \"date\",\n          \"format\": \"epoch_millis\"\n        },\n        \"duration\": {\n          \"type\": \"long\"\n        },\n        \"flags\": {\n          \"type\": \"integer\"\n        },\n        \"logs\": {\n          \"type\": \"nested\",\n          \"dynamic\": false,\n          \"properties\": {\n            \"timestamp\": {\n              \"type\": \"long\"\n            },\n            \"fields\": {\n              \"type\": \"nested\",\n              \"dynamic\": false,\n              \"properties\": {\n                \"key\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"value\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"type\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                }\n              }\n            }\n          }\n        },\n        \"process\": {\n          \"properties\": {\n            \"serviceName\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"tag\": {\n              \"type\": \"object\"\n            },\n            \"tags\": {\n              \"type\": \"nested\",\n              \"dynamic\": false,\n              \"properties\": {\n                \"key\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"value\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"type\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                }\n              }\n            }\n          }\n        },\n        \"references\": {\n          \"type\": \"nested\",\n          \"dynamic\": false,\n          \"properties\": {\n            \"refType\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"traceID\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"spanID\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            }\n          }\n        },\n        \"tag\": {\n          \"type\": \"object\"\n        },\n        \"tags\": {\n          \"type\": \"nested\",\n          \"dynamic\": false,\n          \"properties\": {\n            \"key\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"value\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"type\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/flags.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage mappings\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\n// Options represent configurable parameters for jaeger-esmapping-generator\ntype Options struct {\n\tMapping       string\n\tEsVersion     uint\n\tShards        int64\n\tReplicas      *int64\n\tIndexPrefix   string\n\tUseILM        string // using string as util is being used in python and using bool leads to type issues.\n\tILMPolicyName string\n}\n\nconst (\n\tmappingFlag       = \"mapping\"\n\tesVersionFlag     = \"es-version\"\n\tshardsFlag        = \"shards\"\n\treplicasFlag      = \"replicas\"\n\tindexPrefixFlag   = \"index-prefix\"\n\tuseILMFlag        = \"use-ilm\"\n\tilmPolicyNameFlag = \"ilm-policy-name\"\n)\n\n// AddFlags adds flags for esmapping-generator main program\nfunc (o *Options) AddFlags(command *cobra.Command) {\n\tcommand.Flags().StringVar(\n\t\t&o.Mapping,\n\t\tmappingFlag,\n\t\t\"\",\n\t\t\"The index mapping the template will be applied to. Pass either jaeger-span or jaeger-service\")\n\tcommand.Flags().UintVar(\n\t\t&o.EsVersion,\n\t\tesVersionFlag,\n\t\t7,\n\t\t\"The major Elasticsearch version\")\n\tcommand.Flags().Int64Var(\n\t\t&o.Shards,\n\t\tshardsFlag,\n\t\t5,\n\t\t\"The number of shards per index in Elasticsearch\")\n\t// Allocate storage for Replicas so Int64Var can write into it.\n\to.Replicas = new(int64)\n\tcommand.Flags().Int64Var(\n\t\to.Replicas,\n\t\treplicasFlag,\n\t\t1,\n\t\t\"The number of replicas per index in Elasticsearch\")\n\tcommand.Flags().StringVar(\n\t\t&o.IndexPrefix,\n\t\tindexPrefixFlag,\n\t\t\"\",\n\t\t\"Specifies index prefix\")\n\tcommand.Flags().StringVar(\n\t\t&o.UseILM,\n\t\tuseILMFlag,\n\t\t\"false\",\n\t\t\"Set to true to use ILM for managing lifecycle of jaeger indices\")\n\tcommand.Flags().StringVar(\n\t\t&o.ILMPolicyName,\n\t\tilmPolicyNameFlag,\n\t\t\"jaeger-ilm-policy\",\n\t\t\"The name of the ILM policy to use if ILM is active\")\n\n\t// mark mapping flag as mandatory\n\tcommand.MarkFlagRequired(mappingFlag)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/flags_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage mappings\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptionsWithDefaultFlags(t *testing.T) {\n\to := Options{}\n\tc := cobra.Command{}\n\to.AddFlags(&c)\n\n\tassert.Empty(t, o.Mapping)\n\tassert.Equal(t, uint(7), o.EsVersion)\n\tassert.EqualValues(t, 5, o.Shards)\n\tassert.EqualValues(t, 1, *o.Replicas)\n\n\tassert.Empty(t, o.IndexPrefix)\n\tassert.Equal(t, \"false\", o.UseILM)\n\tassert.Equal(t, \"jaeger-ilm-policy\", o.ILMPolicyName)\n}\n\nfunc TestOptionsWithFlags(t *testing.T) {\n\to := Options{}\n\tc := cobra.Command{}\n\n\to.AddFlags(&c)\n\terr := c.ParseFlags([]string{\n\t\t\"--mapping=jaeger-span\",\n\t\t\"--es-version=7\",\n\t\t\"--shards=5\",\n\t\t\"--replicas=1\",\n\t\t\"--index-prefix=test\",\n\t\t\"--use-ilm=true\",\n\t\t\"--ilm-policy-name=jaeger-test-policy\",\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"jaeger-span\", o.Mapping)\n\tassert.Equal(t, uint(7), o.EsVersion)\n\tassert.Equal(t, int64(5), o.Shards)\n\tassert.Equal(t, int64(1), *o.Replicas)\n\tassert.Equal(t, \"test\", o.IndexPrefix)\n\tassert.Equal(t, \"true\", o.UseILM)\n\tassert.Equal(t, \"jaeger-test-policy\", o.ILMPolicyName)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-dependencies-6.json",
    "content": "{\n  \"template\": \"*jaeger-dependencies-*\",\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-dependencies-7.json",
    "content": "{\n  \"index_patterns\": \"*jaeger-dependencies-*\",\n  {{- if .UseILM }}\n  \"aliases\": {\n    \"{{ .IndexPrefix }}jaeger-dependencies-read\" : {}\n  },\n  {{- end }}\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n  {{- if .UseILM }}\n    ,\"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-dependencies-write\"\n    }\n  {{- end }}\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-dependencies-8.json",
    "content": "{\n  \"priority\": {{ .Priority }},\n  \"index_patterns\": \"{{ .IndexPrefix }}jaeger-dependencies-*\",\n  \"template\": {\n    {{- if .UseILM }}\n    \"aliases\": {\n      \"{{ .IndexPrefix }}jaeger-dependencies-read\": {}\n    },\n    {{- end }}\n    \"settings\": {\n      \"index.number_of_shards\": {{ .Shards }},\n      \"index.number_of_replicas\": {{ .Replicas }},\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true\n      {{- if .UseILM }},\n      \"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-dependencies-write\"\n      }\n      {{- end }}\n    },\n    \"mappings\": {}\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-sampling-6.json",
    "content": "{\n  \"template\": \"*jaeger-sampling-*\",\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":false\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-sampling-7.json",
    "content": "{\n  \"index_patterns\": \"*jaeger-sampling-*\",\n  {{- if .UseILM }}\n  \"aliases\": {\n    \"{{ .IndexPrefix }}jaeger-sampling-read\" : {}\n  },\n  {{- end }}\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":false\n  {{- if .UseILM }}\n    ,\"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-sampling-write\"\n    }\n  {{- end }}\n  },\n  \"mappings\":{}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-sampling-8.json",
    "content": "{\n  \"priority\": {{ .Priority }},\n  \"index_patterns\": \"{{ .IndexPrefix }}jaeger-sampling-*\",\n  \"template\": {\n    {{- if .UseILM }}\n    \"aliases\": {\n      \"{{ .IndexPrefix }}jaeger-sampling-read\": {}\n    },\n    {{- end }}\n    \"settings\": {\n      \"index.number_of_shards\": {{ .Shards }},\n      \"index.number_of_replicas\": {{ .Replicas }},\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": false\n      {{- if .UseILM }},\n      \"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-sampling-write\"\n      }\n      {{- end }}\n    },\n    \"mappings\": {}\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-service-6.json",
    "content": "{\n  \"template\": \"*jaeger-service-*\",\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true,\n    \"index.mapper.dynamic\":false\n  },\n  \"mappings\":{\n    \"_default_\":{\n      \"_all\":{\n        \"enabled\":false\n      },\n      \"dynamic_templates\":[\n        {\n          \"span_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"process.tag.*\"\n          }\n        }\n      ]\n    },\n    \"service\":{\n      \"properties\":{\n        \"serviceName\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"operationName\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-service-7.json",
    "content": "{\n  \"index_patterns\": \"*{{ .IndexPrefix }}jaeger-service-*\",\n  {{- if .UseILM }}\n  \"aliases\": {\n    \"{{ .IndexPrefix }}jaeger-service-read\" : {}\n  },\n  {{- end }}\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n  {{- if .UseILM }}\n    ,\"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-service-write\"\n    }\n  {{- end }}\n  },\n  \"mappings\":{\n    \"dynamic_templates\":[\n      {\n        \"span_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"tag.*\"\n        }\n      },\n      {\n        \"process_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"process.tag.*\"\n        }\n      }\n    ],\n    \"properties\":{\n      \"serviceName\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"operationName\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-service-8.json",
    "content": "{\n  \"priority\": {{ .Priority }},\n  \"index_patterns\": \"{{ .IndexPrefix }}jaeger-service-*\",\n  \"template\": {\n    {{- if .UseILM }}\n    \"aliases\": {\n      \"{{ .IndexPrefix }}jaeger-service-read\": {}\n    },\n    {{- end }}\n    \"settings\": {\n      \"index.number_of_shards\": {{ .Shards }},\n      \"index.number_of_replicas\": {{ .Replicas }},\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true\n      {{- if .UseILM }},\n      \"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-service-write\"\n      }\n      {{- end }}\n    },\n    \"mappings\": {\n      \"dynamic_templates\": [\n        {\n          \"span_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"process.tag.*\"\n          }\n        }\n      ],\n      \"properties\": {\n        \"serviceName\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"operationName\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-span-6.json",
    "content": "{\n  \"template\": \"*jaeger-span-*\",\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true,\n    \"index.mapper.dynamic\":false\n  },\n  \"mappings\":{\n    \"_default_\":{\n      \"_all\":{\n        \"enabled\":false\n      },\n      \"dynamic_templates\":[\n        {\n          \"span_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\":{\n            \"mapping\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"path_match\":\"process.tag.*\"\n          }\n        }\n      ]\n    },\n    \"span\":{\n      \"properties\":{\n        \"traceID\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"parentSpanID\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"spanID\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"operationName\":{\n          \"type\":\"keyword\",\n          \"ignore_above\":256\n        },\n        \"startTime\":{\n          \"type\":\"long\"\n        },\n        \"startTimeMillis\":{\n          \"type\":\"date\",\n          \"format\":\"epoch_millis\"\n        },\n        \"duration\":{\n          \"type\":\"long\"\n        },\n        \"flags\":{\n          \"type\":\"integer\"\n        },\n        \"logs\":{\n          \"type\":\"nested\",\n          \"dynamic\":false,\n          \"properties\":{\n            \"timestamp\":{\n              \"type\":\"long\"\n            },\n            \"fields\":{\n              \"type\":\"nested\",\n              \"dynamic\":false,\n              \"properties\":{\n                \"key\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"value\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"type\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                }\n              }\n            }\n          }\n        },\n        \"process\":{\n          \"properties\":{\n            \"serviceName\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"tag\":{\n              \"type\":\"object\"\n            },\n            \"tags\":{\n              \"type\":\"nested\",\n              \"dynamic\":false,\n              \"properties\":{\n                \"key\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"value\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                },\n                \"type\":{\n                  \"type\":\"keyword\",\n                  \"ignore_above\":256\n                }\n              }\n            }\n          }\n        },\n        \"references\":{\n          \"type\":\"nested\",\n          \"dynamic\":false,\n          \"properties\":{\n            \"refType\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"traceID\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"spanID\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            }\n          }\n        },\n        \"tag\":{\n          \"type\":\"object\"\n        },\n        \"tags\":{\n          \"type\":\"nested\",\n          \"dynamic\":false,\n          \"properties\":{\n            \"key\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"value\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            },\n            \"type\":{\n              \"type\":\"keyword\",\n              \"ignore_above\":256\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-span-7.json",
    "content": "{\n  \"index_patterns\": \"*{{ .IndexPrefix }}jaeger-span-*\",\n  {{- if .UseILM }}\n  \"aliases\": {\n    \"{{ .IndexPrefix }}jaeger-span-read\": {}\n  },\n  {{- end }}\n  \"settings\":{\n    \"index.number_of_shards\": {{ .Shards }},\n    \"index.number_of_replicas\": {{ .Replicas }},\n    \"index.mapping.nested_fields.limit\":50,\n    \"index.requests.cache.enable\":true\n    {{- if .UseILM }}\n    ,\"lifecycle\": {\n      \"name\": \"{{ .ILMPolicyName }}\",\n      \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-span-write\"\n    }\n    {{- end }}\n  },\n  \"mappings\":{\n    \"dynamic_templates\":[\n      {\n        \"span_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"tag.*\"\n        }\n      },\n      {\n        \"process_tags_map\":{\n          \"mapping\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"path_match\":\"process.tag.*\"\n        }\n      }\n    ],\n    \"properties\":{\n      \"traceID\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"parentSpanID\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"spanID\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"operationName\":{\n        \"type\":\"keyword\",\n        \"ignore_above\":256\n      },\n      \"startTime\":{\n        \"type\":\"long\"\n      },\n      \"startTimeMillis\":{\n        \"type\":\"date\",\n        \"format\":\"epoch_millis\"\n      },\n      \"duration\":{\n        \"type\":\"long\"\n      },\n      \"flags\":{\n        \"type\":\"integer\"\n      },\n      \"logs\":{\n        \"type\":\"nested\",\n        \"dynamic\":false,\n        \"properties\":{\n          \"timestamp\":{\n            \"type\":\"long\"\n          },\n          \"fields\":{\n            \"type\":\"nested\",\n            \"dynamic\":false,\n            \"properties\":{\n              \"key\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"value\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"type\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              }\n            }\n          }\n        }\n      },\n      \"process\":{\n        \"properties\":{\n          \"serviceName\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"tag\":{\n            \"type\":\"object\"\n          },\n          \"tags\":{\n            \"type\":\"nested\",\n            \"dynamic\":false,\n            \"properties\":{\n              \"key\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"value\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              },\n              \"type\":{\n                \"type\":\"keyword\",\n                \"ignore_above\":256\n              }\n            }\n          }\n        }\n      },\n      \"references\":{\n        \"type\":\"nested\",\n        \"dynamic\":false,\n        \"properties\":{\n          \"refType\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"traceID\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"spanID\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          }\n        }\n      },\n      \"tag\":{\n        \"type\":\"object\"\n      },\n      \"tags\":{\n        \"type\":\"nested\",\n        \"dynamic\":false,\n        \"properties\":{\n          \"key\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"value\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          },\n          \"type\":{\n            \"type\":\"keyword\",\n            \"ignore_above\":256\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/jaeger-span-8.json",
    "content": "{\n  \"priority\": {{ .Priority }},\n  \"index_patterns\": \"{{ .IndexPrefix }}jaeger-span-*\",\n  \"template\": {\n\n    {{- if .UseILM}}\n    \"aliases\": {\n      \"{{ .IndexPrefix }}jaeger-span-read\": {}\n    },\n    {{- end}}\n    \"settings\": {\n      \"index.number_of_shards\": {{ .Shards }},\n      \"index.number_of_replicas\": {{ .Replicas }},\n      \"index.mapping.nested_fields.limit\": 50,\n      \"index.requests.cache.enable\": true\n      {{- if .UseILM }},\n      \"lifecycle\": {\n        \"name\": \"{{ .ILMPolicyName }}\",\n        \"rollover_alias\": \"{{ .IndexPrefix }}jaeger-span-write\"\n      }\n      {{- end }}\n    },\n    \"mappings\": {\n      \"dynamic_templates\": [\n        {\n          \"span_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"tag.*\"\n          }\n        },\n        {\n          \"process_tags_map\": {\n            \"mapping\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"path_match\": \"process.tag.*\"\n          }\n        }\n      ],\n      \"properties\": {\n        \"traceID\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"parentSpanID\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"spanID\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"operationName\": {\n          \"type\": \"keyword\",\n          \"ignore_above\": 256\n        },\n        \"startTime\": {\n          \"type\": \"long\"\n        },\n        \"startTimeMillis\": {\n          \"type\": \"date\",\n          \"format\": \"epoch_millis\"\n        },\n        \"duration\": {\n          \"type\": \"long\"\n        },\n        \"flags\": {\n          \"type\": \"integer\"\n        },\n        \"logs\": {\n          \"type\": \"nested\",\n          \"dynamic\": false,\n          \"properties\": {\n            \"timestamp\": {\n              \"type\": \"long\"\n            },\n            \"fields\": {\n              \"type\": \"nested\",\n              \"dynamic\": false,\n              \"properties\": {\n                \"key\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"value\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"type\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                }\n              }\n            }\n          }\n        },\n        \"process\": {\n          \"properties\": {\n            \"serviceName\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"tag\": {\n              \"type\": \"object\"\n            },\n            \"tags\": {\n              \"type\": \"nested\",\n              \"dynamic\": false,\n              \"properties\": {\n                \"key\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"value\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                },\n                \"type\": {\n                  \"type\": \"keyword\",\n                  \"ignore_above\": 256\n                }\n              }\n            }\n          }\n        },\n        \"references\": {\n          \"type\": \"nested\",\n          \"dynamic\": false,\n          \"properties\": {\n            \"refType\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"traceID\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"spanID\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            }\n          }\n        },\n        \"tag\": {\n          \"type\": \"object\"\n        },\n        \"tags\": {\n          \"type\": \"nested\",\n          \"dynamic\": false,\n          \"properties\": {\n            \"key\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"value\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            },\n            \"type\": {\n              \"type\": \"keyword\",\n              \"ignore_above\": 256\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/mapping.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage mappings\n\nimport (\n\t\"bytes\"\n\t\"embed\"\n\t\"fmt\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n)\n\n// MAPPINGS contains embedded index templates.\n//\n//go:embed *.json\nvar MAPPINGS embed.FS\n\n// MappingType represents the type of Elasticsearch mapping\ntype MappingType int\n\nconst (\n\tSpanMapping MappingType = iota\n\tServiceMapping\n\tDependenciesMapping\n\tSamplingMapping\n)\n\n// MappingBuilder holds common parameters required to render an elasticsearch index template\ntype MappingBuilder struct {\n\tTemplateBuilder es.TemplateBuilder\n\tIndices         config.Indices\n\tEsVersion       uint\n\tUseILM          bool\n\tILMPolicyName   string\n}\n\n// templateParams holds parameters required to render an elasticsearch index template\ntype templateParams struct {\n\tUseILM        bool\n\tILMPolicyName string\n\tIndexPrefix   string\n\tShards        int64\n\tReplicas      int64\n\tPriority      int64\n}\n\nfunc (mb MappingBuilder) getMappingTemplateOptions(mappingType MappingType) templateParams {\n\tmappingOpts := templateParams{}\n\tmappingOpts.UseILM = mb.UseILM\n\tmappingOpts.ILMPolicyName = mb.ILMPolicyName\n\n\tswitch mappingType {\n\tcase SpanMapping:\n\t\tmappingOpts.Shards = mb.Indices.Spans.Shards\n\t\tmappingOpts.Replicas = *mb.Indices.Spans.Replicas\n\t\tmappingOpts.Priority = mb.Indices.Spans.Priority\n\tcase ServiceMapping:\n\t\tmappingOpts.Shards = mb.Indices.Services.Shards\n\t\tmappingOpts.Replicas = *mb.Indices.Services.Replicas\n\t\tmappingOpts.Priority = mb.Indices.Services.Priority\n\tcase DependenciesMapping:\n\t\tmappingOpts.Shards = mb.Indices.Dependencies.Shards\n\t\tmappingOpts.Replicas = *mb.Indices.Dependencies.Replicas\n\t\tmappingOpts.Priority = mb.Indices.Dependencies.Priority\n\tcase SamplingMapping:\n\t\tmappingOpts.Shards = mb.Indices.Sampling.Shards\n\t\tmappingOpts.Replicas = *mb.Indices.Sampling.Replicas\n\t\tmappingOpts.Priority = mb.Indices.Sampling.Priority\n\tdefault:\n\t\t// Using default values as fallback to avoid breaking functionality.\n\t\tmappingOpts.Shards = 5\n\t\tmappingOpts.Replicas = 1\n\t\tmappingOpts.Priority = 0\n\t}\n\n\treturn mappingOpts\n}\n\nfunc (mt MappingType) String() string {\n\tswitch mt {\n\tcase SpanMapping:\n\t\treturn \"jaeger-span\"\n\tcase ServiceMapping:\n\t\treturn \"jaeger-service\"\n\tcase DependenciesMapping:\n\t\treturn \"jaeger-dependencies\"\n\tcase SamplingMapping:\n\t\treturn \"jaeger-sampling\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\n// MappingTypeFromString converts a string to a MappingType\nfunc MappingTypeFromString(val string) (MappingType, error) {\n\tswitch val {\n\tcase \"jaeger-span\":\n\t\treturn SpanMapping, nil\n\tcase \"jaeger-service\":\n\t\treturn ServiceMapping, nil\n\tcase \"jaeger-dependencies\":\n\t\treturn DependenciesMapping, nil\n\tcase \"jaeger-sampling\":\n\t\treturn SamplingMapping, nil\n\tdefault:\n\t\treturn -1, fmt.Errorf(\"invalid mapping type: %s\", val)\n\t}\n}\n\n// GetMapping returns the rendered mapping based on elasticsearch version\nfunc (mb *MappingBuilder) GetMapping(mappingType MappingType) (string, error) {\n\ttemplateOpts := mb.getMappingTemplateOptions(mappingType)\n\tesVersion := min(mb.EsVersion, 8) // Elasticsearch v9 uses the same template as v8\n\treturn mb.renderMapping(fmt.Sprintf(\"%s-%d.json\", mappingType.String(), esVersion), templateOpts)\n}\n\n// GetSpanServiceMappings returns span and service mappings\nfunc (mb *MappingBuilder) GetSpanServiceMappings() (spanMapping string, serviceMapping string, err error) {\n\tspanMapping, err = mb.GetMapping(SpanMapping)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tserviceMapping, err = mb.GetMapping(ServiceMapping)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\treturn spanMapping, serviceMapping, nil\n}\n\n// GetDependenciesMappings returns dependencies mappings\nfunc (mb *MappingBuilder) GetDependenciesMappings() (string, error) {\n\treturn mb.GetMapping(DependenciesMapping)\n}\n\n// GetSamplingMappings returns sampling mappings\nfunc (mb *MappingBuilder) GetSamplingMappings() (string, error) {\n\treturn mb.GetMapping(SamplingMapping)\n}\n\nfunc loadMapping(name string) string {\n\ts, _ := MAPPINGS.ReadFile(name)\n\treturn string(s)\n}\n\nfunc (mb *MappingBuilder) renderMapping(mapping string, options templateParams) (string, error) {\n\ttmpl, err := mb.TemplateBuilder.Parse(loadMapping(mapping))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\twriter := new(bytes.Buffer)\n\n\toptions.IndexPrefix = mb.Indices.IndexPrefix.Apply(\"\")\n\tif err := tmpl.Execute(writer, options); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn writer.String(), nil\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/mappings/mapping_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage mappings\n\nimport (\n\t\"embed\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"text/template\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\n//go:embed fixtures/*.json\nvar FIXTURES embed.FS\n\nfunc TestMappingBuilderGetMapping(t *testing.T) {\n\ttests := []struct {\n\t\tmapping   MappingType\n\t\tesVersion uint\n\t}{\n\t\t{mapping: SpanMapping, esVersion: 8},\n\t\t{mapping: SpanMapping, esVersion: 7},\n\t\t{mapping: SpanMapping, esVersion: 6},\n\t\t{mapping: ServiceMapping, esVersion: 8},\n\t\t{mapping: ServiceMapping, esVersion: 7},\n\t\t{mapping: ServiceMapping, esVersion: 6},\n\t\t{mapping: DependenciesMapping, esVersion: 8},\n\t\t{mapping: DependenciesMapping, esVersion: 7},\n\t\t{mapping: DependenciesMapping, esVersion: 6},\n\t}\n\tfor _, tt := range tests {\n\t\ttemplateName := tt.mapping.String()\n\n\t\tt.Run(templateName, func(t *testing.T) {\n\t\t\tdefaultOpts := func(p int64) config.IndexOptions {\n\t\t\t\treturn config.IndexOptions{\n\t\t\t\t\tShards:   3,\n\t\t\t\t\tReplicas: new(int64(3)),\n\t\t\t\t\tPriority: p,\n\t\t\t\t}\n\t\t\t}\n\t\t\tserviceOps := defaultOpts(501)\n\t\t\tdependenciesOps := defaultOpts(502)\n\t\t\tsamplingOps := defaultOpts(503)\n\n\t\t\tmb := &MappingBuilder{\n\t\t\t\tTemplateBuilder: es.TextTemplateBuilder{},\n\t\t\t\tIndices: config.Indices{\n\t\t\t\t\tIndexPrefix:  \"test-\",\n\t\t\t\t\tSpans:        defaultOpts(500),\n\t\t\t\t\tServices:     serviceOps,\n\t\t\t\t\tDependencies: dependenciesOps,\n\t\t\t\t\tSampling:     samplingOps,\n\t\t\t\t},\n\t\t\t\tEsVersion:     tt.esVersion,\n\t\t\t\tUseILM:        true,\n\t\t\t\tILMPolicyName: \"jaeger-test-policy\",\n\t\t\t}\n\t\t\tgot, err := mb.GetMapping(tt.mapping)\n\t\t\trequire.NoError(t, err)\n\t\t\tvar wantbytes []byte\n\t\t\tfileSuffix := fmt.Sprintf(\"-%d\", tt.esVersion)\n\t\t\twantbytes, err = FIXTURES.ReadFile(\"fixtures/\" + templateName + fileSuffix + \".json\")\n\t\t\trequire.NoError(t, err)\n\t\t\twant := string(wantbytes)\n\t\t\tassert.Equal(t, want, got)\n\t\t})\n\t}\n}\n\nfunc TestMappingTypeFromString(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected MappingType\n\t\thasError bool\n\t}{\n\t\t{\"jaeger-span\", SpanMapping, false},\n\t\t{\"jaeger-service\", ServiceMapping, false},\n\t\t{\"jaeger-dependencies\", DependenciesMapping, false},\n\t\t{\"jaeger-sampling\", SamplingMapping, false},\n\t\t{\"invalid\", MappingType(-1), true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tresult, err := MappingTypeFromString(tt.input)\n\t\t\tif tt.hasError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Equal(t, \"unknown\", result.String())\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMappingBuilderLoadMapping(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t}{\n\t\t{name: \"jaeger-span-6.json\"},\n\t\t{name: \"jaeger-span-7.json\"},\n\t\t{name: \"jaeger-span-8.json\"},\n\t\t{name: \"jaeger-service-6.json\"},\n\t\t{name: \"jaeger-service-7.json\"},\n\t\t{name: \"jaeger-service-8.json\"},\n\t\t{name: \"jaeger-dependencies-6.json\"},\n\t\t{name: \"jaeger-dependencies-7.json\"},\n\t\t{name: \"jaeger-dependencies-8.json\"},\n\t}\n\tfor _, test := range tests {\n\t\tmapping := loadMapping(test.name)\n\t\tf, err := os.Open(\"./\" + test.name)\n\t\trequire.NoError(t, err)\n\t\tb, err := io.ReadAll(f)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, string(b), mapping)\n\t\t_, err = template.New(\"mapping\").Parse(mapping)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestMappingBuilderFixMapping(t *testing.T) {\n\ttests := []struct {\n\t\tname                    string\n\t\ttemplateBuilderMockFunc func() *mocks.TemplateBuilder\n\t\terr                     string\n\t}{\n\t\t{\n\t\t\tname: \"templateRenderSuccess\",\n\t\t\ttemplateBuilderMockFunc: func() *mocks.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(nil)\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"templateRenderFailure\",\n\t\t\ttemplateBuilderMockFunc: func() *mocks.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template exec error\"))\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"template exec error\",\n\t\t},\n\t\t{\n\t\t\tname: \"templateLoadError\",\n\t\t\ttemplateBuilderMockFunc: func() *mocks.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(nil, errors.New(\"template load error\"))\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"template load error\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tindexTemOps := config.IndexOptions{\n\t\t\t\tShards:   3,\n\t\t\t\tReplicas: new(int64(5)),\n\t\t\t\tPriority: 500,\n\t\t\t}\n\t\t\tmappingBuilder := MappingBuilder{\n\t\t\t\tTemplateBuilder: test.templateBuilderMockFunc(),\n\t\t\t\tIndices: config.Indices{\n\t\t\t\t\tSpans:        indexTemOps,\n\t\t\t\t\tServices:     indexTemOps,\n\t\t\t\t\tDependencies: indexTemOps,\n\t\t\t\t\tSampling:     indexTemOps,\n\t\t\t\t},\n\t\t\t\tEsVersion:     7,\n\t\t\t\tUseILM:        true,\n\t\t\t\tILMPolicyName: \"jaeger-test-policy\",\n\t\t\t}\n\t\t\t_, err := mappingBuilder.renderMapping(\"test\", mappingBuilder.getMappingTemplateOptions(SpanMapping))\n\t\t\tif test.err != \"\" {\n\t\t\t\trequire.EqualError(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMappingBuilderGetSpanServiceMappings(t *testing.T) {\n\ttype args struct {\n\t\tesVersion     uint\n\t\tindexPrefix   string\n\t\tuseILM        bool\n\t\tilmPolicyName string\n\t}\n\ttests := []struct {\n\t\tname                       string\n\t\targs                       args\n\t\tmockNewTextTemplateBuilder func() es.TemplateBuilder\n\t\terr                        string\n\t}{\n\t\t{\n\t\t\tname: \"ES Version 7\",\n\t\t\targs: args{\n\t\t\t\tesVersion:     7,\n\t\t\t\tindexPrefix:   \"test\",\n\t\t\t\tuseILM:        true,\n\t\t\t\tilmPolicyName: \"jaeger-test-policy\",\n\t\t\t},\n\t\t\tmockNewTextTemplateBuilder: func() es.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(nil)\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"ES Version 7 Service Error\",\n\t\t\targs: args{\n\t\t\t\tesVersion:     7,\n\t\t\t\tindexPrefix:   \"test\",\n\t\t\t\tuseILM:        true,\n\t\t\t\tilmPolicyName: \"jaeger-test-policy\",\n\t\t\t},\n\t\t\tmockNewTextTemplateBuilder: func() es.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(nil).Once()\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template load error\")).Once()\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"template load error\",\n\t\t},\n\n\t\t{\n\t\t\tname: \"ES Version < 7\",\n\t\t\targs: args{\n\t\t\t\tesVersion:     6,\n\t\t\t\tindexPrefix:   \"test\",\n\t\t\t\tuseILM:        true,\n\t\t\t\tilmPolicyName: \"jaeger-test-policy\",\n\t\t\t},\n\t\t\tmockNewTextTemplateBuilder: func() es.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(nil)\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"ES Version < 7 Service Error\",\n\t\t\targs: args{\n\t\t\t\tesVersion:     6,\n\t\t\t\tindexPrefix:   \"test\",\n\t\t\t\tuseILM:        true,\n\t\t\t\tilmPolicyName: \"jaeger-test-policy\",\n\t\t\t},\n\t\t\tmockNewTextTemplateBuilder: func() es.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(nil).Once()\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template load error\")).Once()\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"template load error\",\n\t\t},\n\t\t{\n\t\t\tname: \"ES Version < 7 Span Error\",\n\t\t\targs: args{\n\t\t\t\tesVersion:     6,\n\t\t\t\tindexPrefix:   \"test\",\n\t\t\t\tuseILM:        true,\n\t\t\t\tilmPolicyName: \"jaeger-test-policy\",\n\t\t\t},\n\t\t\tmockNewTextTemplateBuilder: func() es.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template load error\"))\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"template load error\",\n\t\t},\n\t\t{\n\t\t\tname: \"ES Version  7 Span Error\",\n\t\t\targs: args{\n\t\t\t\tesVersion:     7,\n\t\t\t\tindexPrefix:   \"test\",\n\t\t\t\tuseILM:        true,\n\t\t\t\tilmPolicyName: \"jaeger-test-policy\",\n\t\t\t},\n\t\t\tmockNewTextTemplateBuilder: func() es.TemplateBuilder {\n\t\t\t\ttb := mocks.TemplateBuilder{}\n\t\t\t\tta := mocks.TemplateApplier{}\n\t\t\t\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template load error\")).Once()\n\t\t\t\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\t\t\t\treturn &tb\n\t\t\t},\n\t\t\terr: \"template load error\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tindexTemOps := config.IndexOptions{\n\t\t\t\tShards:   3,\n\t\t\t\tReplicas: new(int64(3)),\n\t\t\t}\n\n\t\t\tmappingBuilder := MappingBuilder{\n\t\t\t\tTemplateBuilder: test.mockNewTextTemplateBuilder(),\n\t\t\t\tIndices: config.Indices{\n\t\t\t\t\tSpans:        indexTemOps,\n\t\t\t\t\tServices:     indexTemOps,\n\t\t\t\t\tDependencies: indexTemOps,\n\t\t\t\t\tSampling:     indexTemOps,\n\t\t\t\t},\n\t\t\t\tEsVersion:     test.args.esVersion,\n\t\t\t\tUseILM:        test.args.useILM,\n\t\t\t\tILMPolicyName: test.args.ilmPolicyName,\n\t\t\t}\n\t\t\t_, _, err := mappingBuilder.GetSpanServiceMappings()\n\t\t\tif test.err != \"\" {\n\t\t\t\trequire.EqualError(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMappingBuilderGetDependenciesMappings(t *testing.T) {\n\ttb := mocks.TemplateBuilder{}\n\tta := mocks.TemplateApplier{}\n\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template load error\"))\n\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\n\tmappingBuilder := MappingBuilder{\n\t\tTemplateBuilder: &tb,\n\t\tIndices: config.Indices{\n\t\t\tDependencies: config.IndexOptions{\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tShards:   3,\n\t\t\t\tPriority: 10,\n\t\t\t},\n\t\t},\n\t}\n\t_, err := mappingBuilder.GetDependenciesMappings()\n\trequire.EqualError(t, err, \"template load error\")\n}\n\nfunc TestMappingBuilderGetSamplingMappings(t *testing.T) {\n\ttb := mocks.TemplateBuilder{}\n\tta := mocks.TemplateApplier{}\n\tta.On(\"Execute\", mock.Anything, mock.Anything).Return(errors.New(\"template load error\"))\n\ttb.On(\"Parse\", mock.Anything).Return(&ta, nil)\n\n\tmappingBuilder := MappingBuilder{\n\t\tTemplateBuilder: &tb,\n\t\tIndices: config.Indices{\n\t\t\tSampling: config.IndexOptions{\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tShards:   3,\n\t\t\t\tPriority: 10,\n\t\t\t},\n\t\t},\n\t}\n\t_, err := mappingBuilder.GetSamplingMappings()\n\trequire.EqualError(t, err, \"template load error\")\n}\n\nfunc TestGetMappingTemplateOptions_DefaultCase(t *testing.T) {\n\tmappingBuilder := &MappingBuilder{\n\t\tIndices: config.Indices{\n\t\t\tSpans: config.IndexOptions{\n\t\t\t\tShards:   2,\n\t\t\t\tReplicas: new(int64(1)),\n\t\t\t\tPriority: 10,\n\t\t\t},\n\t\t},\n\t\tUseILM:        true,\n\t\tILMPolicyName: \"test-policy\",\n\t}\n\n\topts := mappingBuilder.getMappingTemplateOptions(MappingType(-1))\n\n\tassert.Equal(t, int64(5), opts.Shards)\n\tassert.Equal(t, int64(1), opts.Replicas)\n\tassert.Equal(t, int64(0), opts.Priority)\n\tassert.True(t, opts.UseILM)\n\tassert.Equal(t, \"test-policy\", opts.ILMPolicyName)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/options.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n)\n\nvar defaultIndexOptions = config.IndexOptions{\n\tDateLayout:        initDateLayout(\"day\", \"-\"),\n\tRolloverFrequency: \"day\",\n\tShards:            5,\n\tReplicas:          new(int64(1)),\n\tPriority:          0,\n}\n\n// TODO this should be moved next to config.Configuration struct (maybe ./flags package)\n\n// Options contains various type of Elasticsearch configs and provides the ability\n// to bind them to command line flag and apply overlays, so that some configurations\n// (e.g. archive) may be underspecified and infer the rest of its parameters from primary.\ntype Options struct {\n\tConfig config.Configuration `mapstructure:\",squash\"`\n}\n\nfunc initDateLayout(rolloverFreq, sep string) string {\n\t// default to daily format\n\tindexLayout := \"2006\" + sep + \"01\" + sep + \"02\"\n\tif rolloverFreq == \"hour\" {\n\t\tindexLayout = indexLayout + sep + \"15\"\n\t}\n\treturn indexLayout\n}\n\nfunc DefaultConfig() config.Configuration {\n\treturn config.Configuration{\n\t\tAuthentication: config.Authentication{},\n\t\tSniffing: config.Sniffing{\n\t\t\tEnabled: false,\n\t\t},\n\t\tDisableHealthCheck:       false,\n\t\tMaxSpanAge:               72 * time.Hour,\n\t\tAdaptiveSamplingLookback: 72 * time.Hour,\n\t\tBulkProcessing: config.BulkProcessing{\n\t\t\tMaxBytes:      5 * 1000 * 1000,\n\t\t\tWorkers:       1,\n\t\t\tMaxActions:    1000,\n\t\t\tFlushInterval: time.Millisecond * 200,\n\t\t},\n\t\tTags: config.TagsAsFields{\n\t\t\tDotReplacement: \"@\",\n\t\t},\n\t\tEnabled:              true,\n\t\tCreateIndexTemplates: true,\n\t\tVersion:              0,\n\t\tUseReadWriteAliases:  false,\n\t\tUseILM:               false,\n\t\tServers:              []string{\"http://127.0.0.1:9200\"},\n\t\tRemoteReadClusters:   []string{},\n\t\tMaxDocCount:          10_000,\n\t\tLogLevel:             \"error\",\n\t\tSendGetBodyAs:        \"\",\n\t\tHTTPCompression:      true,\n\t\tIndices: config.Indices{\n\t\t\tSpans:        defaultIndexOptions,\n\t\t\tServices:     defaultIndexOptions,\n\t\t\tDependencies: defaultIndexOptions,\n\t\t\tSampling:     defaultIndexOptions,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/options_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/config/configtls\"\n\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n)\n\nfunc getBasicAuthField(opt configoptional.Optional[escfg.BasicAuthentication], field string) any {\n\tif !opt.HasValue() {\n\t\treturn \"\"\n\t}\n\n\tba := opt.Get()\n\tswitch field {\n\tcase \"Username\":\n\t\treturn ba.Username\n\tcase \"Password\":\n\t\treturn ba.Password\n\tcase \"PasswordFilePath\":\n\t\treturn ba.PasswordFilePath\n\tcase \"ReloadInterval\":\n\t\treturn ba.ReloadInterval\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc getBearerTokenField(opt configoptional.Optional[escfg.TokenAuthentication], field string) any {\n\tif !opt.HasValue() {\n\t\tif field == \"AllowFromContext\" {\n\t\t\treturn false\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tba := opt.Get()\n\tswitch field {\n\tcase \"FilePath\":\n\t\treturn ba.FilePath\n\tcase \"AllowFromContext\":\n\t\treturn ba.AllowFromContext\n\tcase \"ReloadInterval\":\n\t\treturn ba.ReloadInterval\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc getAPIKeyField(opt configoptional.Optional[escfg.TokenAuthentication], field string) any {\n\tif !opt.HasValue() {\n\t\tif field == \"AllowFromContext\" {\n\t\t\treturn false\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tba := opt.Get()\n\tswitch field {\n\tcase \"FilePath\":\n\t\treturn ba.FilePath\n\tcase \"AllowFromContext\":\n\t\treturn ba.AllowFromContext\n\tcase \"ReloadInterval\":\n\t\treturn ba.ReloadInterval\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc TestOptions(t *testing.T) {\n\tprimary := DefaultConfig()\n\n\t// Authentication should not be present when no values are provided\n\tassert.False(t, primary.Authentication.BasicAuthentication.HasValue())\n\tassert.False(t, primary.Authentication.BearerTokenAuth.HasValue())\n\tassert.False(t, primary.Authentication.APIKeyAuth.HasValue())\n\n\tassert.NotEmpty(t, primary.Servers)\n\tassert.Empty(t, primary.RemoteReadClusters)\n\tassert.EqualValues(t, 5, primary.Indices.Spans.Shards)\n\tassert.EqualValues(t, 5, primary.Indices.Services.Shards)\n\tassert.EqualValues(t, 5, primary.Indices.Sampling.Shards)\n\tassert.EqualValues(t, 5, primary.Indices.Dependencies.Shards)\n\trequire.NotNil(t, primary.Indices.Spans.Replicas)\n\tassert.EqualValues(t, 1, *primary.Indices.Spans.Replicas)\n\trequire.NotNil(t, primary.Indices.Services.Replicas)\n\tassert.EqualValues(t, 1, *primary.Indices.Services.Replicas)\n\trequire.NotNil(t, primary.Indices.Sampling.Replicas)\n\tassert.EqualValues(t, 1, *primary.Indices.Sampling.Replicas)\n\trequire.NotNil(t, primary.Indices.Dependencies.Replicas)\n\tassert.EqualValues(t, 1, *primary.Indices.Dependencies.Replicas)\n\tassert.Equal(t, 72*time.Hour, primary.MaxSpanAge)\n\tassert.False(t, primary.Sniffing.Enabled)\n\tassert.False(t, primary.Sniffing.UseHTTPS)\n\tassert.False(t, primary.DisableHealthCheck)\n}\n\nfunc TestOptionsWithFlags(t *testing.T) {\n\tprimary := escfg.Configuration{\n\t\tServers: []string{\"1.1.1.1\", \"2.2.2.2\"},\n\t\tAuthentication: escfg.Authentication{\n\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\tUsername:         \"hello\",\n\t\t\t\tPassword:         \"world\",\n\t\t\t\tPasswordFilePath: \"/foo/bar/baz\",\n\t\t\t\tReloadInterval:   35 * time.Second,\n\t\t\t}),\n\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\tFilePath:         \"/foo/bar\",\n\t\t\t\tAllowFromContext: true,\n\t\t\t\tReloadInterval:   50 * time.Second,\n\t\t\t}),\n\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\tFilePath:         \"/foo/api-key\",\n\t\t\t\tAllowFromContext: true,\n\t\t\t\tReloadInterval:   30 * time.Second,\n\t\t\t}),\n\t\t},\n\t\tRemoteReadClusters: []string{\"cluster_one\", \"cluster_two\"},\n\t\tMaxSpanAge:         48 * time.Hour,\n\t\tSniffing: escfg.Sniffing{\n\t\t\tEnabled:  true,\n\t\t\tUseHTTPS: true,\n\t\t},\n\t\tDisableHealthCheck: true,\n\t\tTLS: configtls.ClientConfig{\n\t\t\tInsecure:           false,\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t\tTags: escfg.TagsAsFields{\n\t\t\tAllAsFields:    true,\n\t\t\tInclude:        \"test,tags\",\n\t\t\tFile:           \"./file.txt\",\n\t\t\tDotReplacement: \"!\",\n\t\t},\n\t\tIndices: escfg.Indices{\n\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\tDateLayout: \"2006010215\", // Go reference time formatted for hourly rollover (yyyy-MM-dd-HH)\n\t\t\t},\n\t\t\tServices: escfg.IndexOptions{\n\t\t\t\tDateLayout: \"20060102\", // Go reference time formatted for daily rollover (yyyy-MM-dd)\n\t\t\t},\n\t\t},\n\t\tUseILM:          true,\n\t\tHTTPCompression: true,\n\t}\n\n\t// Now authentication should be present since values were provided\n\tassert.True(t, primary.Authentication.BasicAuthentication.HasValue())\n\tassert.True(t, primary.Authentication.BearerTokenAuth.HasValue())\n\tassert.True(t, primary.Authentication.APIKeyAuth.HasValue())\n\t// Basic Authentication\n\tassert.Equal(t, \"hello\", getBasicAuthField(primary.Authentication.BasicAuthentication, \"Username\"))\n\tassert.Equal(t, \"world\", getBasicAuthField(primary.Authentication.BasicAuthentication, \"Password\"))\n\tassert.Equal(t, \"/foo/bar/baz\", getBasicAuthField(primary.Authentication.BasicAuthentication, \"PasswordFilePath\"))\n\tassert.Equal(t, 35*time.Second, getBasicAuthField(primary.Authentication.BasicAuthentication, \"ReloadInterval\"))\n\t// Bearer Token Authentication\n\tassert.Equal(t, \"/foo/bar\", getBearerTokenField(primary.Authentication.BearerTokenAuth, \"FilePath\"))\n\tassert.Equal(t, true, getBearerTokenField(primary.Authentication.BearerTokenAuth, \"AllowFromContext\"))\n\tassert.Equal(t, 50*time.Second, getBearerTokenField(primary.Authentication.BearerTokenAuth, \"ReloadInterval\"))\n\t// API Key Authentication\n\tassert.Equal(t, \"/foo/api-key\", getAPIKeyField(primary.Authentication.APIKeyAuth, \"FilePath\"))\n\tassert.Equal(t, true, getAPIKeyField(primary.Authentication.APIKeyAuth, \"AllowFromContext\"))\n\tassert.Equal(t, 30*time.Second, getAPIKeyField(primary.Authentication.APIKeyAuth, \"ReloadInterval\"))\n\t// Server URLs\n\tassert.Equal(t, []string{\"1.1.1.1\", \"2.2.2.2\"}, primary.Servers)\n\t// Remote Read Clusters\n\tassert.Equal(t, []string{\"cluster_one\", \"cluster_two\"}, primary.RemoteReadClusters)\n\t// Max Span Age\n\tassert.Equal(t, 48*time.Hour, primary.MaxSpanAge)\n\t// Sniffing\n\tassert.True(t, primary.Sniffing.Enabled)\n\tassert.True(t, primary.Sniffing.UseHTTPS)\n\tassert.True(t, primary.DisableHealthCheck)\n\t// TLS\n\tassert.False(t, primary.TLS.Insecure)\n\tassert.True(t, primary.TLS.InsecureSkipVerify)\n\t// Tags\n\tassert.True(t, primary.Tags.AllAsFields)\n\tassert.Equal(t, \"!\", primary.Tags.DotReplacement)\n\tassert.Equal(t, \"./file.txt\", primary.Tags.File)\n\tassert.Equal(t, \"test,tags\", primary.Tags.Include)\n\t// Indices\n\tassert.Equal(t, \"20060102\", primary.Indices.Services.DateLayout)\n\tassert.Equal(t, \"2006010215\", primary.Indices.Spans.DateLayout)\n\t// Use ILM\n\tassert.True(t, primary.UseILM)\n\t// HTTP Compression\n\tassert.True(t, primary.HTTPCompression)\n}\n\nfunc TestAuthenticationConditionalCreation(t *testing.T) {\n\ttestCases := []struct {\n\t\tname                           string\n\t\tconfig                         escfg.Configuration\n\t\texpectBasicAuth                bool\n\t\texpectBearerAuth               bool\n\t\texpectAPIKeyAuth               bool\n\t\texpectedUsername               string\n\t\texpectedPassword               string\n\t\texpectedPasswordFilePath       string\n\t\texpectedPasswordReloadInterval time.Duration\n\t\texpectedTokenPath              string\n\t\texpectedBearerFromContext      bool\n\t\texpectedBearerReloadInterval   time.Duration\n\t\texpectedAPIKeyFilePath         string\n\t\texpectedAPIKeyFromContext      bool\n\t\texpectedAPIKeyReloadInterval   time.Duration\n\t}{\n\t\t{\n\t\t\tname: \"no authentication flags\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{},\n\t\t\t},\n\t\t\texpectBasicAuth:  false,\n\t\t\texpectBearerAuth: false,\n\t\t\texpectAPIKeyAuth: false,\n\t\t},\n\t\t{\n\t\t\tname: \"only username provided\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tUsername:       \"testuser\",\n\t\t\t\t\t\tReloadInterval: 10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               false,\n\t\t\texpectAPIKeyAuth:               false,\n\t\t\texpectedUsername:               \"testuser\",\n\t\t\texpectedPasswordReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"only password provided\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tPassword:       \"testpass\",\n\t\t\t\t\t\tReloadInterval: 10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               false,\n\t\t\texpectAPIKeyAuth:               false,\n\t\t\texpectedPassword:               \"testpass\",\n\t\t\texpectedPasswordReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"only token file provided\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/token\",\n\t\t\t\t\t\tAllowFromContext: false,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             false,\n\t\t\texpectedTokenPath:            \"/path/to/token\",\n\t\t\texpectedBearerFromContext:    false,\n\t\t\texpectedBearerReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"username and password provided\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tUsername:       \"testuser\",\n\t\t\t\t\t\tPassword:       \"testpass\",\n\t\t\t\t\t\tReloadInterval: 10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               false,\n\t\t\texpectAPIKeyAuth:               false,\n\t\t\texpectedUsername:               \"testuser\",\n\t\t\texpectedPassword:               \"testpass\",\n\t\t\texpectedPasswordReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"only bearer token context propagation enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             false,\n\t\t\texpectedBearerFromContext:    true,\n\t\t\texpectedBearerReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"both token file and context propagation enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/token\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             false,\n\t\t\texpectedTokenPath:            \"/path/to/token\",\n\t\t\texpectedBearerFromContext:    true,\n\t\t\texpectedBearerReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"bearer token with custom reload interval\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/token\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   45 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             false,\n\t\t\texpectedTokenPath:            \"/path/to/token\",\n\t\t\texpectedBearerFromContext:    true,\n\t\t\texpectedBearerReloadInterval: 45 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"API key all options with zero reload interval\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/keyfile\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   0 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             false,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedAPIKeyFilePath:       \"/path/to/keyfile\",\n\t\t\texpectedAPIKeyFromContext:    true,\n\t\t\texpectedAPIKeyReloadInterval: 0 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"API key with non-zero reload interval\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/keyfile\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   30 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             false,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedAPIKeyFilePath:       \"/path/to/keyfile\",\n\t\t\texpectedAPIKeyFromContext:    true,\n\t\t\texpectedAPIKeyReloadInterval: 30 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"only API key file provided\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/key\",\n\t\t\t\t\t\tAllowFromContext: false,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             false,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedAPIKeyFilePath:       \"/path/to/key\",\n\t\t\texpectedAPIKeyFromContext:    false,\n\t\t\texpectedAPIKeyReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"only API key context propagation enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             false,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedAPIKeyFromContext:    true,\n\t\t\texpectedAPIKeyReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"both API key file and context enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/key\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             false,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedAPIKeyFilePath:       \"/path/to/key\",\n\t\t\texpectedAPIKeyFromContext:    true,\n\t\t\texpectedAPIKeyReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"all API key options provided\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/key\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   60 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             false,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedAPIKeyFilePath:       \"/path/to/key\",\n\t\t\texpectedAPIKeyFromContext:    true,\n\t\t\texpectedAPIKeyReloadInterval: 60 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth and API key both enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tUsername:       \"testuser\",\n\t\t\t\t\t\tPassword:       \"testpass\",\n\t\t\t\t\t\tReloadInterval: 10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:       \"/path/to/key\",\n\t\t\t\t\t\tReloadInterval: 10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               false,\n\t\t\texpectAPIKeyAuth:               true,\n\t\t\texpectedUsername:               \"testuser\",\n\t\t\texpectedPassword:               \"testpass\",\n\t\t\texpectedPasswordReloadInterval: 10 * time.Second,\n\t\t\texpectedAPIKeyFilePath:         \"/path/to/key\",\n\t\t\texpectedAPIKeyReloadInterval:   10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"bearer token and API key both enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/token\",\n\t\t\t\t\t\tAllowFromContext: false,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             true,\n\t\t\texpectedTokenPath:            \"/path/to/token\",\n\t\t\texpectedBearerFromContext:    false,\n\t\t\texpectedBearerReloadInterval: 10 * time.Second,\n\t\t\texpectedAPIKeyFromContext:    true,\n\t\t\texpectedAPIKeyReloadInterval: 10 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth password reload interval disabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tUsername:         \"testuser\",\n\t\t\t\t\t\tPasswordFilePath: \"/path/to/password\",\n\t\t\t\t\t\tReloadInterval:   0 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               false,\n\t\t\texpectAPIKeyAuth:               false,\n\t\t\texpectedUsername:               \"testuser\",\n\t\t\texpectedPasswordFilePath:       \"/path/to/password\",\n\t\t\texpectedPasswordReloadInterval: 0 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"bearer token reload interval disabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:       \"/path/to/token\",\n\t\t\t\t\t\tReloadInterval: 0 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             false,\n\t\t\texpectedTokenPath:            \"/path/to/token\",\n\t\t\texpectedBearerReloadInterval: 0 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"all three authentication methods enabled\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tUsername:       \"testuser\",\n\t\t\t\t\t\tPassword:       \"testpass\",\n\t\t\t\t\t\tReloadInterval: 10 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/token\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   25 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t\tAPIKeyAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:         \"/path/to/key\",\n\t\t\t\t\t\tAllowFromContext: true,\n\t\t\t\t\t\tReloadInterval:   30 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               true,\n\t\t\texpectAPIKeyAuth:               true,\n\t\t\texpectedUsername:               \"testuser\",\n\t\t\texpectedPassword:               \"testpass\",\n\t\t\texpectedPasswordReloadInterval: 10 * time.Second,\n\t\t\texpectedTokenPath:              \"/path/to/token\",\n\t\t\texpectedBearerFromContext:      true,\n\t\t\texpectedBearerReloadInterval:   25 * time.Second,\n\t\t\texpectedAPIKeyFilePath:         \"/path/to/key\",\n\t\t\texpectedAPIKeyFromContext:      true,\n\t\t\texpectedAPIKeyReloadInterval:   30 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth with custom reload interval (non-zero)\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBasicAuthentication: configoptional.Some(escfg.BasicAuthentication{\n\t\t\t\t\t\tUsername:         \"testuser\",\n\t\t\t\t\t\tPasswordFilePath: \"/path/to/password\",\n\t\t\t\t\t\tReloadInterval:   15 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:                true,\n\t\t\texpectBearerAuth:               false,\n\t\t\texpectAPIKeyAuth:               false,\n\t\t\texpectedUsername:               \"testuser\",\n\t\t\texpectedPasswordFilePath:       \"/path/to/password\",\n\t\t\texpectedPasswordReloadInterval: 15 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"bearer token with custom reload interval (non-zero)\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tAuthentication: escfg.Authentication{\n\t\t\t\t\tBearerTokenAuth: configoptional.Some(escfg.TokenAuthentication{\n\t\t\t\t\t\tFilePath:       \"/path/to/token\",\n\t\t\t\t\t\tReloadInterval: 20 * time.Second,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectBasicAuth:              false,\n\t\t\texpectBearerAuth:             true,\n\t\t\texpectAPIKeyAuth:             false,\n\t\t\texpectedTokenPath:            \"/path/to/token\",\n\t\t\texpectedBearerReloadInterval: 20 * time.Second,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tprimary := tc.config\n\n\t\t\t// Assert authentication method presence\n\t\t\tassert.Equal(t, tc.expectBasicAuth, primary.Authentication.BasicAuthentication.HasValue())\n\t\t\tassert.Equal(t, tc.expectBearerAuth, primary.Authentication.BearerTokenAuth.HasValue())\n\t\t\tassert.Equal(t, tc.expectAPIKeyAuth, primary.Authentication.APIKeyAuth.HasValue())\n\n\t\t\t// Assert basic authentication details\n\t\t\tif tc.expectBasicAuth {\n\t\t\t\tbasicAuth := primary.Authentication.BasicAuthentication.Get()\n\t\t\t\tassert.Equal(t, tc.expectedUsername, basicAuth.Username)\n\t\t\t\tassert.Equal(t, tc.expectedPassword, basicAuth.Password)\n\t\t\t\tassert.Equal(t, tc.expectedPasswordFilePath, basicAuth.PasswordFilePath)\n\t\t\t\tassert.Equal(t, tc.expectedPasswordReloadInterval, basicAuth.ReloadInterval)\n\t\t\t}\n\n\t\t\t// Assert bearer token authentication details\n\t\t\tif tc.expectBearerAuth {\n\t\t\t\tbearerAuth := primary.Authentication.BearerTokenAuth.Get()\n\t\t\t\tassert.Equal(t, tc.expectedTokenPath, bearerAuth.FilePath)\n\t\t\t\tassert.Equal(t, tc.expectedBearerFromContext, bearerAuth.AllowFromContext)\n\t\t\t\tassert.Equal(t, tc.expectedBearerReloadInterval, bearerAuth.ReloadInterval)\n\t\t\t}\n\n\t\t\t// Assert API key authentication details\n\t\t\tif tc.expectAPIKeyAuth {\n\t\t\t\tapiKeyAuth := primary.Authentication.APIKeyAuth.Get()\n\t\t\t\tassert.Equal(t, tc.expectedAPIKeyFilePath, apiKeyAuth.FilePath)\n\t\t\t\tassert.Equal(t, tc.expectedAPIKeyFromContext, apiKeyAuth.AllowFromContext)\n\t\t\t\tassert.Equal(t, tc.expectedAPIKeyReloadInterval, apiKeyAuth.ReloadInterval)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetBasicAuthField_DefaultCase(t *testing.T) {\n\tbasicAuth := escfg.BasicAuthentication{\n\t\tUsername:         \"test-user\",\n\t\tPassword:         \"test-pass\",\n\t\tPasswordFilePath: \"/path/to/file\",\n\t}\n\n\topt := configoptional.Some(basicAuth)\n\n\tresult := getBasicAuthField(opt, \"UnknownField\")\n\tassert.Empty(t, result)\n}\n\nfunc TestEmptyRemoteReadClusters(t *testing.T) {\n\tprimary := escfg.Configuration{\n\t\tRemoteReadClusters: []string{},\n\t}\n\tassert.Equal(t, []string{}, primary.RemoteReadClusters)\n}\n\nfunc TestMaxSpanAgeSetErrorInArchiveMode(t *testing.T) {\n\t// This test verifies that max-span-age flag is not available in archive mode\n\t// Since we're not testing flags anymore, we just verify that the behavior is documented\n\t// In archive mode, MaxSpanAge should not be used (traces are searched with no look-back limit)\n\tt.Skip(\"Test for flag parsing behavior - no longer applicable with direct config initialization\")\n}\n\nfunc TestMaxDocCount(t *testing.T) {\n\ttestCases := []struct {\n\t\tname            string\n\t\tconfig          escfg.Configuration\n\t\twantMaxDocCount int\n\t}{\n\t\t{\n\t\t\tname:            \"default value\",\n\t\t\tconfig:          DefaultConfig(),\n\t\t\twantMaxDocCount: 10_000,\n\t\t},\n\t\t{\n\t\t\tname: \"custom value\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tMaxDocCount: 1000,\n\t\t\t},\n\t\t\twantMaxDocCount: 1000,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.wantMaxDocCount, tc.config.MaxDocCount)\n\t\t})\n\t}\n}\n\nfunc TestIndexDateSeparator(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tconfig         escfg.Configuration\n\t\twantDateLayout string\n\t}{\n\t\t{\n\t\t\tname:           \"default separator\",\n\t\t\tconfig:         DefaultConfig(),\n\t\t\twantDateLayout: \"2006-01-02\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty separator\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout: \"20060102\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantDateLayout: \"20060102\",\n\t\t},\n\t\t{\n\t\t\tname: \"dot separator\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout: \"2006.01.02\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantDateLayout: \"2006.01.02\",\n\t\t},\n\t\t{\n\t\t\tname: \"dash separator\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout: \"2006-01-02\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantDateLayout: \"2006-01-02\",\n\t\t},\n\t\t{\n\t\t\tname: \"slash separator\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout: \"2006/01/02\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantDateLayout: \"2006/01/02\",\n\t\t},\n\t\t{\n\t\t\tname: \"single quote separator\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout: \"2006''01''02\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantDateLayout: \"2006''01''02\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.wantDateLayout, tc.config.Indices.Spans.DateLayout)\n\t\t})\n\t}\n}\n\nfunc TestIndexRollover(t *testing.T) {\n\ttestCases := []struct {\n\t\tname                              string\n\t\tconfig                            escfg.Configuration\n\t\twantSpanDateLayout                string\n\t\twantServiceDateLayout             string\n\t\twantSpanIndexRolloverFrequency    time.Duration\n\t\twantServiceIndexRolloverFrequency time.Duration\n\t}{\n\t\t{\n\t\t\tname:                              \"default\",\n\t\t\tconfig:                            DefaultConfig(),\n\t\t\twantSpanDateLayout:                \"2006-01-02\",\n\t\t\twantServiceDateLayout:             \"2006-01-02\",\n\t\t\twantSpanIndexRolloverFrequency:    -24 * time.Hour,\n\t\t\twantServiceIndexRolloverFrequency: -24 * time.Hour,\n\t\t},\n\t\t{\n\t\t\tname: \"hourly spans, daily services\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout:        \"2006-01-02-15\",\n\t\t\t\t\t\tRolloverFrequency: \"hour\",\n\t\t\t\t\t},\n\t\t\t\t\tServices: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout:        \"2006-01-02\",\n\t\t\t\t\t\tRolloverFrequency: \"day\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantSpanDateLayout:                \"2006-01-02-15\",\n\t\t\twantServiceDateLayout:             \"2006-01-02\",\n\t\t\twantSpanIndexRolloverFrequency:    -1 * time.Hour,\n\t\t\twantServiceIndexRolloverFrequency: -24 * time.Hour,\n\t\t},\n\t\t{\n\t\t\tname: \"daily spans, hourly services\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout:        \"2006-01-02\",\n\t\t\t\t\t\tRolloverFrequency: \"day\",\n\t\t\t\t\t},\n\t\t\t\t\tServices: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout:        \"2006-01-02-15\",\n\t\t\t\t\t\tRolloverFrequency: \"hour\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantSpanDateLayout:                \"2006-01-02\",\n\t\t\twantServiceDateLayout:             \"2006-01-02-15\",\n\t\t\twantSpanIndexRolloverFrequency:    -24 * time.Hour,\n\t\t\twantServiceIndexRolloverFrequency: -1 * time.Hour,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid rollover frequency defaults to day\",\n\t\t\tconfig: escfg.Configuration{\n\t\t\t\tIndices: escfg.Indices{\n\t\t\t\t\tSpans: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout:        \"2006-01-02\",\n\t\t\t\t\t\tRolloverFrequency: \"hours\",\n\t\t\t\t\t},\n\t\t\t\t\tServices: escfg.IndexOptions{\n\t\t\t\t\t\tDateLayout:        \"2006-01-02\",\n\t\t\t\t\t\tRolloverFrequency: \"hours\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantSpanDateLayout:                \"2006-01-02\",\n\t\t\twantServiceDateLayout:             \"2006-01-02\",\n\t\t\twantSpanIndexRolloverFrequency:    -24 * time.Hour,\n\t\t\twantServiceIndexRolloverFrequency: -24 * time.Hour,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.wantSpanDateLayout, tc.config.Indices.Spans.DateLayout)\n\t\t\tassert.Equal(t, tc.wantServiceDateLayout, tc.config.Indices.Services.DateLayout)\n\t\t\tassert.Equal(t, tc.wantSpanIndexRolloverFrequency, escfg.RolloverFrequencyAsNegativeDuration(tc.config.Indices.Spans.RolloverFrequency))\n\t\t\tassert.Equal(t, tc.wantServiceIndexRolloverFrequency, escfg.RolloverFrequencyAsNegativeDuration(tc.config.Indices.Services.RolloverFrequency))\n\t\t})\n\t}\n}\n\n// TestAddFlags and TestAddFlagsWithPreExistingAuth were removed as they tested\n// flag registration behavior which is no longer relevant after moving to direct config initialization\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/samplingstore/dbmodel/converter.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nfunc FromThroughputs(throughputs []*model.Throughput) []Throughput {\n\tif throughputs == nil {\n\t\treturn nil\n\t}\n\tret := make([]Throughput, len(throughputs))\n\tfor i, d := range throughputs {\n\t\tret[i] = Throughput{\n\t\t\tService:       d.Service,\n\t\t\tOperation:     d.Operation,\n\t\t\tCount:         d.Count,\n\t\t\tProbabilities: d.Probabilities,\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc ToThroughputs(throughputs []Throughput) []*model.Throughput {\n\tif throughputs == nil {\n\t\treturn nil\n\t}\n\tret := make([]*model.Throughput, len(throughputs))\n\tfor i, d := range throughputs {\n\t\tret[i] = &model.Throughput{\n\t\t\tService:       d.Service,\n\t\t\tOperation:     d.Operation,\n\t\t\tCount:         d.Count,\n\t\t\tProbabilities: d.Probabilities,\n\t\t}\n\t}\n\treturn ret\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/samplingstore/dbmodel/converter_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestConvertDependencies(t *testing.T) {\n\ttests := []struct {\n\t\tthroughputs []*model.Throughput\n\t}{\n\t\t{\n\t\t\tthroughputs: []*model.Throughput{{Service: \"service1\", Operation: \"operation1\", Count: 10, Probabilities: map[string]struct{}{\"new-srv\": {}}}},\n\t\t},\n\t\t{\n\t\t\tthroughputs: []*model.Throughput{},\n\t\t},\n\t\t{\n\t\t\tthroughputs: nil,\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tgot := FromThroughputs(test.throughputs)\n\t\t\ta := ToThroughputs(got)\n\t\t\tassert.Equal(t, test.throughputs, a)\n\t\t})\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/samplingstore/dbmodel/model.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"time\"\n)\n\ntype Throughput struct {\n\tService       string\n\tOperation     string\n\tCount         int64\n\tProbabilities map[string]struct{}\n}\n\ntype TimeThroughput struct {\n\tTimestamp  time.Time  `json:\"timestamp\"`\n\tThroughput Throughput `json:\"throughputs\"`\n}\n\ntype ProbabilitiesAndQPS struct {\n\tHostname      string\n\tProbabilities map[string]map[string]float64\n\tQPS           map[string]map[string]float64\n}\n\ntype TimeProbabilitiesAndQPS struct {\n\tTimestamp           time.Time           `json:\"timestamp\"`\n\tProbabilitiesAndQPS ProbabilitiesAndQPS `json:\"probabilitiesandqps\"`\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/samplingstore/storage.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.uber.org/zap\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\tesquery \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/samplingstore/dbmodel\"\n)\n\nconst (\n\tsamplingIndexBaseName = \"jaeger-sampling\"\n\tthroughputType        = \"throughput-sampling\"\n\tprobabilitiesType     = \"probabilities-sampling\"\n)\n\ntype SamplingStore struct {\n\tclient                 func() es.Client\n\tlogger                 *zap.Logger\n\tsamplingIndexPrefix    string\n\tindexDateLayout        string\n\tmaxDocCount            int\n\tindexRolloverFrequency time.Duration\n\tlookback               time.Duration\n}\n\ntype Params struct {\n\tClient                 func() es.Client\n\tLogger                 *zap.Logger\n\tIndexPrefix            config.IndexPrefix\n\tIndexDateLayout        string\n\tIndexRolloverFrequency time.Duration\n\tLookback               time.Duration\n\tMaxDocCount            int\n}\n\nfunc NewSamplingStore(p Params) *SamplingStore {\n\treturn &SamplingStore{\n\t\tclient:                 p.Client,\n\t\tlogger:                 p.Logger,\n\t\tsamplingIndexPrefix:    p.PrefixedIndexName() + config.IndexPrefixSeparator,\n\t\tindexDateLayout:        p.IndexDateLayout,\n\t\tmaxDocCount:            p.MaxDocCount,\n\t\tindexRolloverFrequency: p.IndexRolloverFrequency,\n\t\tlookback:               p.Lookback,\n\t}\n}\n\nfunc (s *SamplingStore) InsertThroughput(throughput []*model.Throughput) error {\n\tts := time.Now()\n\tindexName := indexWithDate(s.samplingIndexPrefix, s.indexDateLayout, ts)\n\tfor _, eachThroughput := range dbmodel.FromThroughputs(throughput) {\n\t\ts.client().Index().Index(indexName).Type(throughputType).\n\t\t\tBodyJson(&dbmodel.TimeThroughput{\n\t\t\t\tTimestamp:  ts,\n\t\t\t\tThroughput: eachThroughput,\n\t\t\t}).Add()\n\t}\n\treturn nil\n}\n\nfunc (s *SamplingStore) GetThroughput(start, end time.Time) ([]*model.Throughput, error) {\n\tctx := context.Background()\n\tindices := getReadIndices(s.samplingIndexPrefix, s.indexDateLayout, start, end, s.indexRolloverFrequency)\n\tsearchResult, err := s.client().Search(indices...).\n\t\tSize(s.maxDocCount).\n\t\tQuery(buildTSQuery(start, end)).\n\t\tIgnoreUnavailable(true).\n\t\tDo(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to search for throughputs: %w\", err)\n\t}\n\toutput := make([]dbmodel.TimeThroughput, len(searchResult.Hits.Hits))\n\tfor i, hit := range searchResult.Hits.Hits {\n\t\tif err := json.Unmarshal(hit.Source, &output[i]); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unmarshalling documents failed: %w\", err)\n\t\t}\n\t}\n\toutThroughputs := make([]dbmodel.Throughput, len(output))\n\tfor i, out := range output {\n\t\toutThroughputs[i] = out.Throughput\n\t}\n\treturn dbmodel.ToThroughputs(outThroughputs), nil\n}\n\nfunc (s *SamplingStore) InsertProbabilitiesAndQPS(hostname string,\n\tprobabilities model.ServiceOperationProbabilities,\n\tqps model.ServiceOperationQPS,\n) error {\n\tts := time.Now()\n\twriteIndexName := indexWithDate(s.samplingIndexPrefix, s.indexDateLayout, ts)\n\tval := dbmodel.ProbabilitiesAndQPS{\n\t\tHostname:      hostname,\n\t\tProbabilities: probabilities,\n\t\tQPS:           qps,\n\t}\n\ts.writeProbabilitiesAndQPS(writeIndexName, ts, val)\n\treturn nil\n}\n\nfunc (s *SamplingStore) GetLatestProbabilities() (model.ServiceOperationProbabilities, error) {\n\tctx := context.Background()\n\tclientFn := s.client()\n\tindices, err := getLatestIndices(s.samplingIndexPrefix, s.indexDateLayout, clientFn, s.indexRolloverFrequency, s.lookback)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get latest indices: %w\", err)\n\t}\n\tsearchResult, err := clientFn.Search(indices...).\n\t\tSize(s.maxDocCount).\n\t\tIgnoreUnavailable(true).\n\t\tDo(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to search for Latest Probabilities: %w\", err)\n\t}\n\tlengthOfSearchResult := len(searchResult.Hits.Hits)\n\tif lengthOfSearchResult == 0 {\n\t\treturn nil, nil\n\t}\n\n\tvar latestProbabilities dbmodel.TimeProbabilitiesAndQPS\n\tlatestTime := time.Time{}\n\tfor _, hit := range searchResult.Hits.Hits {\n\t\tvar data dbmodel.TimeProbabilitiesAndQPS\n\t\tif err = json.Unmarshal(hit.Source, &data); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unmarshalling documents failed: %w\", err)\n\t\t}\n\t\tif data.Timestamp.After(latestTime) {\n\t\t\tlatestTime = data.Timestamp\n\t\t\tlatestProbabilities = data\n\t\t}\n\t}\n\treturn latestProbabilities.ProbabilitiesAndQPS.Probabilities, nil\n}\n\nfunc (s *SamplingStore) writeProbabilitiesAndQPS(indexName string, ts time.Time, pandqps dbmodel.ProbabilitiesAndQPS) {\n\ts.client().Index().Index(indexName).Type(probabilitiesType).\n\t\tBodyJson(&dbmodel.TimeProbabilitiesAndQPS{\n\t\t\tTimestamp:           ts,\n\t\t\tProbabilitiesAndQPS: pandqps,\n\t\t}).Add()\n}\n\nfunc getLatestIndices(indexPrefix, indexDateLayout string, clientFn es.Client, rollover time.Duration, maxDuration time.Duration) ([]string, error) {\n\tctx := context.Background()\n\tnow := time.Now().UTC()\n\tearliest := now.Add(-maxDuration)\n\tearliestIndex := indexWithDate(indexPrefix, indexDateLayout, earliest)\n\tfor {\n\t\tcurrentIndex := indexWithDate(indexPrefix, indexDateLayout, now)\n\t\texists, err := clientFn.IndexExists(currentIndex).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to check index existence: %w\", err)\n\t\t}\n\t\tif exists {\n\t\t\treturn []string{currentIndex}, nil\n\t\t}\n\t\tif currentIndex == earliestIndex {\n\t\t\treturn nil, errors.New(\"falied to find latest index\")\n\t\t}\n\t\tnow = now.Add(rollover) // rollover is negative\n\t}\n}\n\nfunc getReadIndices(indexName, indexDateLayout string, startTime time.Time, endTime time.Time, rollover time.Duration) []string {\n\tvar indices []string\n\tfirstIndex := indexWithDate(indexName, indexDateLayout, startTime)\n\tcurrentIndex := indexWithDate(indexName, indexDateLayout, endTime)\n\tfor currentIndex != firstIndex {\n\t\tindices = append(indices, currentIndex)\n\t\tendTime = endTime.Add(rollover) // rollover is negative\n\t\tcurrentIndex = indexWithDate(indexName, indexDateLayout, endTime)\n\t}\n\tindices = append(indices, firstIndex)\n\treturn indices\n}\n\nfunc (p *Params) PrefixedIndexName() string {\n\treturn p.IndexPrefix.Apply(samplingIndexBaseName)\n}\n\nfunc buildTSQuery(start, end time.Time) elastic.Query {\n\treturn esquery.NewRangeQuery(\"timestamp\").Gte(start).Lte(end)\n}\n\nfunc indexWithDate(indexNamePrefix, indexDateLayout string, date time.Time) string {\n\treturn indexNamePrefix + date.UTC().Format(indexDateLayout)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/samplingstore/storage_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage samplingstore\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n\tsamplemodel \"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/samplingstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst defaultMaxDocCount = 10_000\n\ntype samplingStorageTest struct {\n\tclient    *mocks.Client\n\tlogger    *zap.Logger\n\tlogBuffer *testutils.Buffer\n\tstorage   *SamplingStore\n}\n\nfunc withEsSampling(indexPrefix config.IndexPrefix, indexDateLayout string, maxDocCount int, fn func(w *samplingStorageTest)) {\n\tclient := &mocks.Client{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tw := &samplingStorageTest{\n\t\tclient:    client,\n\t\tlogger:    logger,\n\t\tlogBuffer: logBuffer,\n\t\tstorage: NewSamplingStore(Params{\n\t\t\tClient:          func() es.Client { return client },\n\t\t\tLogger:          logger,\n\t\t\tIndexPrefix:     indexPrefix,\n\t\t\tIndexDateLayout: indexDateLayout,\n\t\t\tMaxDocCount:     maxDocCount,\n\t\t}),\n\t}\n\tfn(w)\n}\n\nfunc TestNewIndexPrefix(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tprefix   config.IndexPrefix\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"without prefix\",\n\t\t\tprefix:   \"\",\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with prefix\",\n\t\t\tprefix:   \"foo\",\n\t\t\texpected: \"foo-\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tclient := &mocks.Client{}\n\t\t\tr := NewSamplingStore(Params{\n\t\t\t\tClient:          func() es.Client { return client },\n\t\t\t\tLogger:          zap.NewNop(),\n\t\t\t\tIndexPrefix:     test.prefix,\n\t\t\t\tIndexDateLayout: \"2006-01-02\",\n\t\t\t\tMaxDocCount:     defaultMaxDocCount,\n\t\t\t})\n\t\t\tassert.Equal(t, test.expected+samplingIndexBaseName+config.IndexPrefixSeparator, r.samplingIndexPrefix)\n\t\t})\n\t}\n}\n\nfunc TestGetReadIndices(t *testing.T) {\n\ttest := struct {\n\t\tname  string\n\t\tstart time.Time\n\t\tend   time.Time\n\t}{\n\t\tname:  \"\",\n\t\tstart: time.Date(2024, time.February, 10, 0, 0, 0, 0, time.UTC),\n\t\tend:   time.Date(2024, time.February, 12, 0, 0, 0, 0, time.UTC),\n\t}\n\tt.Run(test.name, func(t *testing.T) {\n\t\texpectedIndices := []string{\n\t\t\t\"prefix-jaeger-sampling-2024-02-12\",\n\t\t\t\"prefix-jaeger-sampling-2024-02-11\",\n\t\t\t\"prefix-jaeger-sampling-2024-02-10\",\n\t\t}\n\t\trollover := -time.Hour * 24\n\t\tindices := getReadIndices(\"prefix-jaeger-sampling-\", \"2006-01-02\", test.start, test.end, rollover)\n\t\tassert.Equal(t, expectedIndices, indices)\n\t})\n}\n\nfunc TestGetLatestIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tindexDateLayout string\n\t\tmaxDuration     time.Duration\n\t\texpectedIndices []string\n\t\texpectedError   string\n\t\tIndexExistError error\n\t\tindexExist      bool\n\t}{\n\t\t{\n\t\t\tname:            \"with index\",\n\t\t\tindexDateLayout: \"2006-01-02\",\n\t\t\tmaxDuration:     24 * time.Hour,\n\t\t\texpectedIndices: []string{indexWithDate(\"\", \"2006-01-02\", time.Now().UTC())},\n\t\t\texpectedError:   \"\",\n\t\t\tindexExist:      true,\n\t\t},\n\t\t{\n\t\t\tname:            \"without index\",\n\t\t\tindexDateLayout: \"2006-01-02\",\n\t\t\tmaxDuration:     72 * time.Hour,\n\t\t\texpectedError:   \"falied to find latest index\",\n\t\t\tindexExist:      false,\n\t\t},\n\t\t{\n\t\t\tname:            \"check index existence\",\n\t\t\tindexDateLayout: \"2006-01-02\",\n\t\t\tmaxDuration:     24 * time.Hour,\n\t\t\texpectedError:   \"failed to check index existence: fail\",\n\t\t\tindexExist:      true,\n\t\t\tIndexExistError: errors.New(\"fail\"),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\twithEsSampling(\"\", test.indexDateLayout, defaultMaxDocCount, func(w *samplingStorageTest) {\n\t\t\t\tindexService := &mocks.IndicesExistsService{}\n\t\t\t\tw.client.On(\"IndexExists\", mock.Anything).Return(indexService)\n\t\t\t\tindexService.On(\"Do\", mock.Anything).Return(test.indexExist, test.IndexExistError)\n\t\t\t\tclientFnMock := w.storage.client()\n\t\t\t\tactualIndices, err := getLatestIndices(\"\", test.indexDateLayout, clientFnMock, -24*time.Hour, test.maxDuration)\n\t\t\t\tif test.expectedError != \"\" {\n\t\t\t\t\trequire.EqualError(t, err, test.expectedError)\n\t\t\t\t\tassert.Nil(t, actualIndices)\n\t\t\t\t} else {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, test.expectedIndices, actualIndices)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInsertThroughput(t *testing.T) {\n\ttest := struct {\n\t\tname          string\n\t\texpectedError string\n\t}{\n\t\tname:          \"insert throughput\",\n\t\texpectedError: \"\",\n\t}\n\n\tt.Run(test.name, func(t *testing.T) {\n\t\twithEsSampling(\"\", \"2006-01-02\", defaultMaxDocCount, func(w *samplingStorageTest) {\n\t\t\tthroughputs := []*samplemodel.Throughput{\n\t\t\t\t{Service: \"my-svc\", Operation: \"op\"},\n\t\t\t\t{Service: \"our-svc\", Operation: \"op2\"},\n\t\t\t}\n\t\t\tfixedTime := time.Now()\n\t\t\tindexName := indexWithDate(\"\", \"2006-01-02\", fixedTime)\n\t\t\twriteService := &mocks.IndexService{}\n\t\t\tw.client.On(\"Index\").Return(writeService)\n\t\t\twriteService.On(\"Index\", stringMatcher(indexName)).Return(writeService)\n\t\t\twriteService.On(\"Type\", stringMatcher(throughputType)).Return(writeService)\n\t\t\twriteService.On(\"BodyJson\", mock.Anything).Return(writeService)\n\t\t\twriteService.On(\"Add\", mock.Anything)\n\t\t\terr := w.storage.InsertThroughput(throughputs)\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.EqualError(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestInsertProbabilitiesAndQPS(t *testing.T) {\n\ttest := struct {\n\t\tname          string\n\t\texpectedError string\n\t}{\n\t\tname:          \"insert probabilities and qps\",\n\t\texpectedError: \"\",\n\t}\n\n\tt.Run(test.name, func(t *testing.T) {\n\t\twithEsSampling(\"\", \"2006-01-02\", defaultMaxDocCount, func(w *samplingStorageTest) {\n\t\t\tpAQ := dbmodel.ProbabilitiesAndQPS{\n\t\t\t\tHostname:      \"dell11eg843d\",\n\t\t\t\tProbabilities: samplemodel.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}},\n\t\t\t\tQPS:           samplemodel.ServiceOperationQPS{\"new-srv\": {\"op\": 4}},\n\t\t\t}\n\t\t\tfixedTime := time.Now()\n\t\t\tindexName := indexWithDate(\"\", \"2006-01-02\", fixedTime)\n\t\t\twriteService := &mocks.IndexService{}\n\t\t\tw.client.On(\"Index\").Return(writeService)\n\t\t\twriteService.On(\"Index\", stringMatcher(indexName)).Return(writeService)\n\t\t\twriteService.On(\"Type\", stringMatcher(probabilitiesType)).Return(writeService)\n\t\t\twriteService.On(\"BodyJson\", mock.Anything).Return(writeService)\n\t\t\twriteService.On(\"Add\", mock.Anything)\n\t\t\terr := w.storage.InsertProbabilitiesAndQPS(pAQ.Hostname, pAQ.Probabilities, pAQ.QPS)\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.EqualError(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestGetThroughput(t *testing.T) {\n\tmockIndex := \"jaeger-sampling-\" + time.Now().UTC().Format(\"2006-01-02\")\n\tgoodThroughputs := `{\n\t\t\t\"timestamp\": \"2024-02-08T12:00:00Z\",\n\t\t\t\"throughputs\": {\n\t\t\t\t\t\"Service\": \"my-svc\",\n\t\t\t\t\t\"Operation\": \"op\",\n\t\t\t\t\t\"Count\": 10\n\t\t\t}\n\t}`\n\ttests := []struct {\n\t\tname           string\n\t\tsearchResult   *elastic.SearchResult\n\t\tsearchError    error\n\t\texpectedError  string\n\t\texpectedOutput []*samplemodel.Throughput\n\t\tindexPrefix    config.IndexPrefix\n\t\tmaxDocCount    int\n\t\tindex          string\n\t}{\n\t\t{\n\t\t\tname:         \"good throughputs without prefix\",\n\t\t\tsearchResult: createSearchResult(goodThroughputs),\n\t\t\texpectedOutput: []*samplemodel.Throughput{\n\t\t\t\t{\n\t\t\t\t\tService:   \"my-svc\",\n\t\t\t\t\tOperation: \"op\",\n\t\t\t\t\tCount:     10,\n\t\t\t\t},\n\t\t\t},\n\t\t\tindex:       mockIndex,\n\t\t\tmaxDocCount: 1000,\n\t\t},\n\t\t{\n\t\t\tname:         \"good throughputs without prefix\",\n\t\t\tsearchResult: createSearchResult(goodThroughputs),\n\t\t\texpectedOutput: []*samplemodel.Throughput{\n\t\t\t\t{\n\t\t\t\t\tService:   \"my-svc\",\n\t\t\t\t\tOperation: \"op\",\n\t\t\t\t\tCount:     10,\n\t\t\t\t},\n\t\t\t},\n\t\t\tindex:       mockIndex,\n\t\t\tmaxDocCount: 1000,\n\t\t},\n\t\t{\n\t\t\tname:         \"good throughputs with prefix\",\n\t\t\tsearchResult: createSearchResult(goodThroughputs),\n\t\t\texpectedOutput: []*samplemodel.Throughput{\n\t\t\t\t{\n\t\t\t\t\tService:   \"my-svc\",\n\t\t\t\t\tOperation: \"op\",\n\t\t\t\t\tCount:     10,\n\t\t\t\t},\n\t\t\t},\n\t\t\tindex:       mockIndex,\n\t\t\tindexPrefix: \"foo\",\n\t\t\tmaxDocCount: 1000,\n\t\t},\n\t\t{\n\t\t\tname:          \"bad throughputs\",\n\t\t\tsearchResult:  createSearchResult(`badJson{hello}world`),\n\t\t\texpectedError: \"unmarshalling documents failed: invalid character 'b' looking for beginning of value\",\n\t\t\tindex:         mockIndex,\n\t\t},\n\t\t{\n\t\t\tname:          \"search fails\",\n\t\t\tsearchError:   errors.New(\"search failure\"),\n\t\t\texpectedError: \"failed to search for throughputs: search failure\",\n\t\t\tindex:         mockIndex,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\twithEsSampling(test.indexPrefix, \"2006-01-02\", defaultMaxDocCount, func(w *samplingStorageTest) {\n\t\t\t\tsearchService := &mocks.SearchService{}\n\t\t\t\tif test.indexPrefix != \"\" {\n\t\t\t\t\ttest.indexPrefix += \"-\"\n\t\t\t\t}\n\t\t\t\tindex := test.indexPrefix.Apply(test.index)\n\t\t\t\tw.client.On(\"Search\", []string{index}).Return(searchService)\n\t\t\t\tsearchService.On(\"Size\", mock.Anything).Return(searchService)\n\t\t\t\tsearchService.On(\"Query\", mock.Anything).Return(searchService)\n\t\t\t\tsearchService.On(\"IgnoreUnavailable\", true).Return(searchService)\n\t\t\t\tsearchService.On(\"Do\", mock.Anything).Return(test.searchResult, test.searchError)\n\n\t\t\t\tactual, err := w.storage.GetThroughput(time.Now().Add(-time.Minute), time.Now())\n\t\t\t\tif test.expectedError != \"\" {\n\t\t\t\t\trequire.EqualError(t, err, test.expectedError)\n\t\t\t\t\tassert.Nil(t, actual)\n\t\t\t\t} else {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.Equal(t, test.expectedOutput, actual)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestGetLatestProbabilities(t *testing.T) {\n\tmockIndex := \"jaeger-sampling-\" + time.Now().UTC().Format(\"2006-01-02\")\n\tgoodProbabilities := `{\n\t\t\"timestamp\": \"2024-02-08T12:00:00Z\",\n\t\t\"probabilitiesandqps\": {\n\t\t\t\"Hostname\": \"dell11eg843d\",\n\t\t\t\"Probabilities\": {\n\t\t\t\t\"new-srv\": {\"op\": 0.1}\n\t\t\t},\n\t\t\t\"QPS\": {\n\t\t\t\t\"new-srv\": {\"op\": 4}\n\t\t\t}\n\t\t}\n\t}`\n\ttests := []struct {\n\t\tname           string\n\t\tsearchResult   *elastic.SearchResult\n\t\tsearchError    error\n\t\texpectedOutput samplemodel.ServiceOperationProbabilities\n\t\texpectedError  string\n\t\tmaxDocCount    int\n\t\tindex          string\n\t\tindexPresent   bool\n\t\tindexError     error\n\t\tindexPrefix    config.IndexPrefix\n\t}{\n\t\t{\n\t\t\tname:         \"good probabilities without prefix\",\n\t\t\tsearchResult: createSearchResult(goodProbabilities),\n\t\t\texpectedOutput: samplemodel.ServiceOperationProbabilities{\n\t\t\t\t\"new-srv\": {\n\t\t\t\t\t\"op\": 0.1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tindex:        mockIndex,\n\t\t\tmaxDocCount:  1000,\n\t\t\tindexPresent: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"good probabilities with prefix\",\n\t\t\tsearchResult: createSearchResult(goodProbabilities),\n\t\t\texpectedOutput: samplemodel.ServiceOperationProbabilities{\n\t\t\t\t\"new-srv\": {\n\t\t\t\t\t\"op\": 0.1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tindex:        mockIndex,\n\t\t\tmaxDocCount:  1000,\n\t\t\tindexPresent: true,\n\t\t\tindexPrefix:  \"foo\",\n\t\t},\n\t\t{\n\t\t\tname:          \"bad probabilities\",\n\t\t\tsearchResult:  createSearchResult(`badJson{hello}world`),\n\t\t\texpectedError: \"unmarshalling documents failed: invalid character 'b' looking for beginning of value\",\n\t\t\tindex:         mockIndex,\n\t\t\tindexPresent:  true,\n\t\t},\n\t\t{\n\t\t\tname:          \"search fail\",\n\t\t\tsearchError:   errors.New(\"search failure\"),\n\t\t\texpectedError: \"failed to search for Latest Probabilities: search failure\",\n\t\t\tindex:         mockIndex,\n\t\t\tindexPresent:  true,\n\t\t},\n\t\t{\n\t\t\tname:          \"index check fail\",\n\t\t\tindexError:    errors.New(\"index check failure\"),\n\t\t\texpectedError: \"failed to get latest indices: failed to check index existence: index check failure\",\n\t\t\tindex:         mockIndex,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\twithEsSampling(test.indexPrefix, \"2006-01-02\", defaultMaxDocCount, func(w *samplingStorageTest) {\n\t\t\t\tsearchService := &mocks.SearchService{}\n\t\t\t\tindex := test.indexPrefix.Apply(test.index)\n\t\t\t\tw.client.On(\"Search\", []string{index}).Return(searchService)\n\t\t\t\tsearchService.On(\"Size\", mock.Anything).Return(searchService)\n\t\t\t\tsearchService.On(\"IgnoreUnavailable\", true).Return(searchService)\n\t\t\t\tsearchService.On(\"Do\", mock.Anything).Return(test.searchResult, test.searchError)\n\n\t\t\t\tindicesexistsservice := &mocks.IndicesExistsService{}\n\t\t\t\tw.client.On(\"IndexExists\", index).Return(indicesexistsservice)\n\t\t\t\tindicesexistsservice.On(\"Do\", mock.Anything).Return(test.indexPresent, test.indexError)\n\n\t\t\t\tactual, err := w.storage.GetLatestProbabilities()\n\t\t\t\tif test.expectedError != \"\" {\n\t\t\t\t\trequire.EqualError(t, err, test.expectedError)\n\t\t\t\t\tassert.Nil(t, actual)\n\t\t\t\t} else {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.Equal(t, test.expectedOutput, actual)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc createSearchResult(rawJsonStr string) *elastic.SearchResult {\n\trawJsonArr := []byte(rawJsonStr)\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: rawJsonArr,\n\t}\n\tsearchResult := &elastic.SearchResult{Hits: &elastic.SearchHits{Hits: hits}}\n\treturn searchResult\n}\n\nfunc stringMatcher(q string) any {\n\tmatchFunc := func(s string) bool {\n\t\treturn strings.Contains(s, q)\n\t}\n\treturn mock.MatchedBy(matchFunc)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/core_span_reader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\n// CoreSpanReader is a DB-Level abstraction which directly deals with database level operations\ntype CoreSpanReader interface {\n\t// FindTraceIDs retrieves traces IDs that match the traceQuery\n\tFindTraceIDs(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.TraceID, error)\n\t// FindTraces retrieves traces that match the traceQuery\n\tFindTraces(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.Trace, error)\n\t// GetOperations returns all operations for a specific service traced by Jaeger\n\tGetOperations(ctx context.Context, query dbmodel.OperationQueryParameters) ([]dbmodel.Operation, error)\n\t// GetServices returns all services traced by Jaeger, ordered by frequency\n\tGetServices(ctx context.Context) ([]string, error)\n\t// GetTraces takes a traceID and returns a Trace associated with that traceID\n\tGetTraces(ctx context.Context, query []dbmodel.TraceID) ([]dbmodel.Trace, error)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/fixtures/domain_01.json",
    "content": "{\n  \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n  \"spanId\": \"AAAAAAAAAAI=\",\n  \"operationName\": \"test-general-conversion\",\n  \"references\": [\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAM=\"\n    },\n    {\n      \"refType\": \"FOLLOWS_FROM\",\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAQ=\"\n    },\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAA/w==\",\n      \"spanId\": \"AAAAAAAAAP8=\"\n    }\n  ],\n  \"flags\": 1,\n  \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n  \"duration\": \"5000ns\",\n  \"tags\": [\n    {\n      \"key\": \"peer.service\",\n      \"vType\": \"STRING\",\n      \"vStr\": \"service-y\"\n    },\n    {\n      \"key\": \"peer.ipv4\",\n      \"vType\": \"INT64\",\n      \"vInt64\": 23456\n    },\n    {\n      \"key\": \"error\",\n      \"vType\": \"BOOL\",\n      \"vBool\": true\n    },\n    {\n      \"key\": \"temperature\",\n      \"vType\": \"FLOAT64\",\n      \"vFloat64\": 72.5\n    },\n    {\n      \"key\": \"blob\",\n      \"vType\": \"BINARY\",\n      \"vBinary\": \"AAAwOQ==\"\n    }\n  ],\n  \"logs\": [\n    {\n      \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"fields\": [\n        {\n          \"key\": \"event\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 123415\n        }\n      ]\n    },\n    {\n      \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"fields\": [\n        {\n          \"key\": \"x\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"process\": {\n    \"serviceName\": \"service-x\",\n    \"tags\": [\n      {\n        \"key\": \"peer.ipv4\",\n        \"vType\": \"INT64\",\n        \"vInt64\": 23456\n      },\n      {\n        \"key\": \"error\",\n        \"vType\": \"BOOL\",\n        \"vBool\": true\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/fixtures/es_01.json",
    "content": "{\n  \"traceID\": \"0000000000000001\",\n  \"spanID\": \"0000000000000002\",\n  \"flags\": 1,\n  \"operationName\": \"test-general-conversion\",\n  \"references\": [\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000003\"\n    },\n    {\n      \"refType\": \"FOLLOWS_FROM\",\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000004\"\n    },\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"00000000000000ff\",\n      \"spanID\": \"00000000000000ff\"\n    }\n  ],\n  \"startTime\": 1485467191639875,\n  \"startTimeMillis\": 1485467191639,\n  \"duration\": 5,\n  \"tags\": [\n    {\n      \"key\": \"peer.service\",\n      \"type\": \"string\",\n      \"value\": \"service-y\"\n    },\n    {\n      \"key\": \"peer.ipv4\",\n      \"type\": \"int64\",\n      \"value\": 23456\n    },\n    {\n      \"key\": \"error\",\n      \"type\": \"bool\",\n      \"value\": true\n    },\n    {\n      \"key\": \"temperature\",\n      \"type\": \"float64\",\n      \"value\": 72.5\n    },\n    {\n      \"key\": \"blob\",\n      \"type\": \"binary\",\n      \"value\": \"00003039\"\n    }\n  ],\n  \"logs\": [\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"event\",\n          \"type\": \"int64\",\n          \"value\": 123415\n        }\n      ]\n    },\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"x\",\n          \"type\": \"string\",\n          \"value\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"process\": {\n    \"serviceName\": \"service-x\",\n    \"tags\": [\n      {\n        \"key\": \"peer.ipv4\",\n        \"type\": \"int64\",\n        \"value\": 23456\n      },\n      {\n        \"key\": \"error\",\n        \"type\": \"bool\",\n        \"value\": true\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/fixtures/query_01.json",
    "content": "{\n  \"bool\":{\n    \"should\":[\n      {\n        \"bool\":{\n          \"must\":{\n            \"regexp\":{\n              \"tag.bat@foo\":{\n                \"value\":\"spook\"\n              }\n            }\n          }\n        }\n      },\n      {\n        \"bool\":{\n          \"must\":{\n            \"regexp\":{\n              \"process.tag.bat@foo\":{\n                \"value\":\"spook\"\n              }\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"tags\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"tags.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"tags.value\":{\n                      \"value\":\"spook\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"process.tags\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"process.tags.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"process.tags.value\":{\n                      \"value\":\"spook\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"logs.fields\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"logs.fields.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"logs.fields.value\":{\n                      \"value\":\"spook\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/fixtures/query_02.json",
    "content": "{\n  \"bool\":{\n    \"should\":[\n      {\n        \"bool\":{\n          \"must\":{\n            \"regexp\":{\n              \"tag.bat@foo\":{\n                \"value\":\"spo.*\"\n              }\n            }\n          }\n        }\n      },\n      {\n        \"bool\":{\n          \"must\":{\n            \"regexp\":{\n              \"process.tag.bat@foo\":{\n                \"value\":\"spo.*\"\n              }\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"tags\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"tags.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"tags.value\":{\n                      \"value\":\"spo.*\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"process.tags\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"process.tags.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"process.tags.value\":{\n                      \"value\":\"spo.*\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"logs.fields\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"logs.fields.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"logs.fields.value\":{\n                      \"value\":\"spo.*\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/fixtures/query_03.json",
    "content": "{\n  \"bool\":{\n    \"should\":[\n      {\n        \"bool\":{\n          \"must\":{\n            \"regexp\":{\n              \"tag.bat@foo\":{\n                \"value\":\"spo\\\\*\"\n              }\n            }\n          }\n        }\n      },\n      {\n        \"bool\":{\n          \"must\":{\n            \"regexp\":{\n              \"process.tag.bat@foo\":{\n                \"value\":\"spo\\\\*\"\n              }\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"tags\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"tags.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"tags.value\":{\n                      \"value\":\"spo\\\\*\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"process.tags\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"process.tags.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"process.tags.value\":{\n                      \"value\":\"spo\\\\*\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      },\n      {\n        \"nested\":{\n          \"path\":\"logs.fields\",\n          \"query\":{\n            \"bool\":{\n              \"must\":[\n                {\n                  \"match\":{\n                    \"logs.fields.key\":{\n                      \"query\":\"bat.foo\"\n                    }\n                  }\n                },\n                {\n                  \"regexp\":{\n                    \"logs.fields.value\":{\n                      \"value\":\"spo\\\\*\"\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/from_domain.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"strings\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\n// NewFromDomain creates FromDomain used to convert model span to db span\nfunc NewFromDomain() FromDomain {\n\treturn FromDomain{}\n}\n\n// FromDomain is used to convert model span to db span\ntype FromDomain struct{}\n\n// FromDomainEmbedProcess converts model.Span into json.Span format.\n// This format includes a ParentSpanID and an embedded Process.\nfunc (fd FromDomain) FromDomainEmbedProcess(span *model.Span) *dbmodel.Span {\n\ts := fd.convertSpanInternal(span)\n\ts.Process = fd.convertProcess(span.Process)\n\ts.References = fd.convertReferences(span)\n\treturn &s\n}\n\nfunc (fd FromDomain) convertSpanInternal(span *model.Span) dbmodel.Span {\n\ttags := fd.convertKeyValues(span.Tags)\n\treturn dbmodel.Span{\n\t\tTraceID:         dbmodel.TraceID(span.TraceID.String()),\n\t\tSpanID:          dbmodel.SpanID(span.SpanID.String()),\n\t\tFlags:           uint32(span.Flags),\n\t\tOperationName:   span.OperationName,\n\t\tStartTime:       model.TimeAsEpochMicroseconds(span.StartTime),\n\t\tStartTimeMillis: model.TimeAsEpochMicroseconds(span.StartTime) / 1000,\n\t\tDuration:        model.DurationAsMicroseconds(span.Duration),\n\t\tTags:            tags,\n\t\tLogs:            fd.convertLogs(span.Logs),\n\t}\n}\n\nfunc (fd FromDomain) convertReferences(span *model.Span) []dbmodel.Reference {\n\tout := make([]dbmodel.Reference, 0, len(span.References))\n\tfor _, ref := range span.References {\n\t\tout = append(out, dbmodel.Reference{\n\t\t\tRefType: fd.convertRefType(ref.RefType),\n\t\t\tTraceID: dbmodel.TraceID(ref.TraceID.String()),\n\t\t\tSpanID:  dbmodel.SpanID(ref.SpanID.String()),\n\t\t})\n\t}\n\treturn out\n}\n\nfunc (FromDomain) convertRefType(refType model.SpanRefType) dbmodel.ReferenceType {\n\tif refType == model.FollowsFrom {\n\t\treturn dbmodel.FollowsFrom\n\t}\n\treturn dbmodel.ChildOf\n}\n\nfunc (fd FromDomain) convertKeyValues(keyValues model.KeyValues) []dbmodel.KeyValue {\n\tkvs := make([]dbmodel.KeyValue, 0, len(keyValues))\n\tfor i := range keyValues {\n\t\tkvs = append(kvs, fd.convertKeyValue(keyValues[i]))\n\t}\n\tif kvs == nil {\n\t\tkvs = make([]dbmodel.KeyValue, 0)\n\t}\n\treturn kvs\n}\n\nfunc (fd FromDomain) convertLogs(logs []model.Log) []dbmodel.Log {\n\tout := make([]dbmodel.Log, len(logs))\n\tfor i, log := range logs {\n\t\tvar kvs []dbmodel.KeyValue\n\t\tfor _, kv := range log.Fields {\n\t\t\tkvs = append(kvs, fd.convertKeyValue(kv))\n\t\t}\n\t\tout[i] = dbmodel.Log{\n\t\t\tTimestamp: model.TimeAsEpochMicroseconds(log.Timestamp),\n\t\t\tFields:    kvs,\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (fd FromDomain) convertProcess(process *model.Process) dbmodel.Process {\n\ttags := fd.convertKeyValues(process.Tags)\n\treturn dbmodel.Process{\n\t\tServiceName: process.ServiceName,\n\t\tTags:        tags,\n\t}\n}\n\nfunc (FromDomain) convertKeyValue(kv model.KeyValue) dbmodel.KeyValue {\n\toutKv := dbmodel.KeyValue{\n\t\tKey:  kv.Key,\n\t\tType: dbmodel.ValueType(strings.ToLower(kv.VType.String())),\n\t}\n\tif kv.GetVType() == model.BinaryType {\n\t\toutKv.Value = kv.AsString()\n\t} else {\n\t\toutKv.Value = kv.Value()\n\t}\n\treturn outKv\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/from_domain_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\nconst NumberOfFixtures = 1\n\nfunc TestFromDomainEmbedProcess(t *testing.T) {\n\tfor i := 1; i <= NumberOfFixtures; i++ {\n\t\tt.Run(fmt.Sprintf(\"fixture_%d\", i), func(t *testing.T) {\n\t\t\tdomainStr, jsonStr := loadFixtures(t, i)\n\n\t\t\tvar span model.Span\n\t\t\trequire.NoError(t, jsonpb.Unmarshal(bytes.NewReader(domainStr), &span))\n\t\t\tconverter := NewFromDomain()\n\t\t\tembeddedSpan := converter.FromDomainEmbedProcess(&span)\n\n\t\t\ttestJSONEncoding(t, i, jsonStr, embeddedSpan)\n\n\t\t\tCompareJSONSpans(t, jsonStr, embeddedSpan)\n\t\t})\n\t}\n}\n\n// Loads and returns domain model and JSON model fixtures with given number i.\nfunc loadFixtures(t *testing.T, i int) (inStr []byte, outStr []byte) {\n\tvar err error\n\tin := fmt.Sprintf(\"fixtures/domain_%02d.json\", i)\n\tinStr, err = os.ReadFile(in)\n\trequire.NoError(t, err)\n\tout := fmt.Sprintf(\"fixtures/es_%02d.json\", i)\n\toutStr, err = os.ReadFile(out)\n\trequire.NoError(t, err)\n\treturn inStr, outStr\n}\n\nfunc testJSONEncoding(t *testing.T, i int, expectedStr []byte, object any) {\n\tbuf := &bytes.Buffer{}\n\tenc := json.NewEncoder(buf)\n\tenc.SetIndent(\"\", \"  \")\n\n\toutFile := fmt.Sprintf(\"fixtures/es_%02d\", i)\n\trequire.NoError(t, enc.Encode(object))\n\n\tif !assert.Equal(t, string(expectedStr), buf.String()) {\n\t\terr := os.WriteFile(outFile+\"-actual.json\", buf.Bytes(), 0o644)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestEmptyTags(t *testing.T) {\n\ttags := make([]model.KeyValue, 0)\n\tspan := model.Span{Tags: tags, Process: &model.Process{Tags: tags}}\n\tconverter := NewFromDomain()\n\tdbSpan := converter.FromDomainEmbedProcess(&span)\n\tassert.Empty(t, dbSpan.Tags)\n\tassert.Empty(t, dbSpan.Tag)\n}\n\nfunc TestConvertKeyValueValue(t *testing.T) {\n\tlongString := `Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues\n\tBender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues\n\tBender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues\n\tBender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues\n\tBender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues Bender Bending Rodrigues `\n\tkey := \"key\"\n\ttests := []struct {\n\t\tkv       model.KeyValue\n\t\texpected dbmodel.KeyValue\n\t}{\n\t\t{\n\t\t\tkv:       model.Bool(key, true),\n\t\t\texpected: dbmodel.KeyValue{Key: key, Value: true, Type: \"bool\"},\n\t\t},\n\t\t{\n\t\t\tkv:       model.Bool(key, false),\n\t\t\texpected: dbmodel.KeyValue{Key: key, Value: false, Type: \"bool\"},\n\t\t},\n\t\t{\n\t\t\tkv:       model.Int64(key, int64(1499)),\n\t\t\texpected: dbmodel.KeyValue{Key: key, Value: int64(1499), Type: \"int64\"},\n\t\t},\n\t\t{\n\t\t\tkv:       model.Float64(key, float64(15.66)),\n\t\t\texpected: dbmodel.KeyValue{Key: key, Value: 15.66, Type: \"float64\"},\n\t\t},\n\t\t{\n\t\t\tkv:       model.String(key, longString),\n\t\t\texpected: dbmodel.KeyValue{Key: key, Value: longString, Type: \"string\"},\n\t\t},\n\t\t{\n\t\t\tkv:       model.Binary(key, []byte(longString)),\n\t\t\texpected: dbmodel.KeyValue{Key: key, Value: hex.EncodeToString([]byte(longString)), Type: \"binary\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(fmt.Sprintf(\"%s:%s\", test.expected.Type, test.expected.Key), func(t *testing.T) {\n\t\t\tconverter := NewFromDomain()\n\t\t\tactual := converter.convertKeyValue(test.kv)\n\t\t\tassert.Equal(t, test.expected, actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/index_utils.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"time\"\n)\n\n// returns index name with date\nfunc indexWithDate(indexPrefix, indexDateLayout string, date time.Time) string {\n\tspanDate := date.UTC().Format(indexDateLayout)\n\treturn indexPrefix + spanDate\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/json_span_compare_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\nfunc CompareJSONSpans(t *testing.T, expected []byte, actual *dbmodel.Span) {\n\tbuf := &bytes.Buffer{}\n\tenc := json.NewEncoder(buf)\n\tenc.SetIndent(\"\", \"  \")\n\trequire.NoError(t, enc.Encode(actual))\n\tif !assert.Equal(t, string(expected), buf.String()) {\n\t\tfor _, err := range pretty.Diff(expected, actual) {\n\t\t\tt.Log(err)\n\t\t}\n\t\tout, err := json.Marshal(actual)\n\t\trequire.NoError(t, err)\n\t\tt.Logf(\"Actual trace: %s\", string(out))\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewCoreSpanReader creates a new instance of CoreSpanReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewCoreSpanReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *CoreSpanReader {\n\tmock := &CoreSpanReader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// CoreSpanReader is an autogenerated mock type for the CoreSpanReader type\ntype CoreSpanReader struct {\n\tmock.Mock\n}\n\ntype CoreSpanReader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *CoreSpanReader) EXPECT() *CoreSpanReader_Expecter {\n\treturn &CoreSpanReader_Expecter{mock: &_m.Mock}\n}\n\n// FindTraceIDs provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) FindTraceIDs(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.TraceID, error) {\n\tret := _mock.Called(ctx, traceQuery)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraceIDs\")\n\t}\n\n\tvar r0 []dbmodel.TraceID\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, dbmodel.TraceQueryParameters) ([]dbmodel.TraceID, error)); ok {\n\t\treturn returnFunc(ctx, traceQuery)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, dbmodel.TraceQueryParameters) []dbmodel.TraceID); ok {\n\t\tr0 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]dbmodel.TraceID)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, dbmodel.TraceQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_FindTraceIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraceIDs'\ntype CoreSpanReader_FindTraceIDs_Call struct {\n\t*mock.Call\n}\n\n// FindTraceIDs is a helper method to define mock.On call\n//   - ctx context.Context\n//   - traceQuery dbmodel.TraceQueryParameters\nfunc (_e *CoreSpanReader_Expecter) FindTraceIDs(ctx interface{}, traceQuery interface{}) *CoreSpanReader_FindTraceIDs_Call {\n\treturn &CoreSpanReader_FindTraceIDs_Call{Call: _e.mock.On(\"FindTraceIDs\", ctx, traceQuery)}\n}\n\nfunc (_c *CoreSpanReader_FindTraceIDs_Call) Run(run func(ctx context.Context, traceQuery dbmodel.TraceQueryParameters)) *CoreSpanReader_FindTraceIDs_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 dbmodel.TraceQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(dbmodel.TraceQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraceIDs_Call) Return(traceIDs []dbmodel.TraceID, err error) *CoreSpanReader_FindTraceIDs_Call {\n\t_c.Call.Return(traceIDs, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraceIDs_Call) RunAndReturn(run func(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.TraceID, error)) *CoreSpanReader_FindTraceIDs_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// FindTraces provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) FindTraces(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.Trace, error) {\n\tret := _mock.Called(ctx, traceQuery)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraces\")\n\t}\n\n\tvar r0 []dbmodel.Trace\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, dbmodel.TraceQueryParameters) ([]dbmodel.Trace, error)); ok {\n\t\treturn returnFunc(ctx, traceQuery)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, dbmodel.TraceQueryParameters) []dbmodel.Trace); ok {\n\t\tr0 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]dbmodel.Trace)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, dbmodel.TraceQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, traceQuery)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_FindTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraces'\ntype CoreSpanReader_FindTraces_Call struct {\n\t*mock.Call\n}\n\n// FindTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - traceQuery dbmodel.TraceQueryParameters\nfunc (_e *CoreSpanReader_Expecter) FindTraces(ctx interface{}, traceQuery interface{}) *CoreSpanReader_FindTraces_Call {\n\treturn &CoreSpanReader_FindTraces_Call{Call: _e.mock.On(\"FindTraces\", ctx, traceQuery)}\n}\n\nfunc (_c *CoreSpanReader_FindTraces_Call) Run(run func(ctx context.Context, traceQuery dbmodel.TraceQueryParameters)) *CoreSpanReader_FindTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 dbmodel.TraceQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(dbmodel.TraceQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraces_Call) Return(traces []dbmodel.Trace, err error) *CoreSpanReader_FindTraces_Call {\n\t_c.Call.Return(traces, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_FindTraces_Call) RunAndReturn(run func(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.Trace, error)) *CoreSpanReader_FindTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperations provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetOperations(ctx context.Context, query dbmodel.OperationQueryParameters) ([]dbmodel.Operation, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperations\")\n\t}\n\n\tvar r0 []dbmodel.Operation\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, dbmodel.OperationQueryParameters) ([]dbmodel.Operation, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, dbmodel.OperationQueryParameters) []dbmodel.Operation); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]dbmodel.Operation)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, dbmodel.OperationQueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetOperations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperations'\ntype CoreSpanReader_GetOperations_Call struct {\n\t*mock.Call\n}\n\n// GetOperations is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query dbmodel.OperationQueryParameters\nfunc (_e *CoreSpanReader_Expecter) GetOperations(ctx interface{}, query interface{}) *CoreSpanReader_GetOperations_Call {\n\treturn &CoreSpanReader_GetOperations_Call{Call: _e.mock.On(\"GetOperations\", ctx, query)}\n}\n\nfunc (_c *CoreSpanReader_GetOperations_Call) Run(run func(ctx context.Context, query dbmodel.OperationQueryParameters)) *CoreSpanReader_GetOperations_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 dbmodel.OperationQueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(dbmodel.OperationQueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetOperations_Call) Return(operations []dbmodel.Operation, err error) *CoreSpanReader_GetOperations_Call {\n\t_c.Call.Return(operations, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetOperations_Call) RunAndReturn(run func(ctx context.Context, query dbmodel.OperationQueryParameters) ([]dbmodel.Operation, error)) *CoreSpanReader_GetOperations_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetServices provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetServices(ctx context.Context) ([]string, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetServices\")\n\t}\n\n\tvar r0 []string\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]string)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServices'\ntype CoreSpanReader_GetServices_Call struct {\n\t*mock.Call\n}\n\n// GetServices is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *CoreSpanReader_Expecter) GetServices(ctx interface{}) *CoreSpanReader_GetServices_Call {\n\treturn &CoreSpanReader_GetServices_Call{Call: _e.mock.On(\"GetServices\", ctx)}\n}\n\nfunc (_c *CoreSpanReader_GetServices_Call) Run(run func(ctx context.Context)) *CoreSpanReader_GetServices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetServices_Call) Return(strings []string, err error) *CoreSpanReader_GetServices_Call {\n\t_c.Call.Return(strings, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetServices_Call) RunAndReturn(run func(ctx context.Context) ([]string, error)) *CoreSpanReader_GetServices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetTraces provides a mock function for the type CoreSpanReader\nfunc (_mock *CoreSpanReader) GetTraces(ctx context.Context, query []dbmodel.TraceID) ([]dbmodel.Trace, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTraces\")\n\t}\n\n\tvar r0 []dbmodel.Trace\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, []dbmodel.TraceID) ([]dbmodel.Trace, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, []dbmodel.TraceID) []dbmodel.Trace); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]dbmodel.Trace)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, []dbmodel.TraceID) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreSpanReader_GetTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTraces'\ntype CoreSpanReader_GetTraces_Call struct {\n\t*mock.Call\n}\n\n// GetTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query []dbmodel.TraceID\nfunc (_e *CoreSpanReader_Expecter) GetTraces(ctx interface{}, query interface{}) *CoreSpanReader_GetTraces_Call {\n\treturn &CoreSpanReader_GetTraces_Call{Call: _e.mock.On(\"GetTraces\", ctx, query)}\n}\n\nfunc (_c *CoreSpanReader_GetTraces_Call) Run(run func(ctx context.Context, query []dbmodel.TraceID)) *CoreSpanReader_GetTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 []dbmodel.TraceID\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].([]dbmodel.TraceID)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetTraces_Call) Return(traces []dbmodel.Trace, err error) *CoreSpanReader_GetTraces_Call {\n\t_c.Call.Return(traces, err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanReader_GetTraces_Call) RunAndReturn(run func(ctx context.Context, query []dbmodel.TraceID) ([]dbmodel.Trace, error)) *CoreSpanReader_GetTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewCoreSpanWriter creates a new instance of CoreSpanWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewCoreSpanWriter(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *CoreSpanWriter {\n\tmock := &CoreSpanWriter{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// CoreSpanWriter is an autogenerated mock type for the CoreSpanWriter type\ntype CoreSpanWriter struct {\n\tmock.Mock\n}\n\ntype CoreSpanWriter_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *CoreSpanWriter) EXPECT() *CoreSpanWriter_Expecter {\n\treturn &CoreSpanWriter_Expecter{mock: &_m.Mock}\n}\n\n// Close provides a mock function for the type CoreSpanWriter\nfunc (_mock *CoreSpanWriter) Close() error {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Close\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func() error); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// CoreSpanWriter_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'\ntype CoreSpanWriter_Close_Call struct {\n\t*mock.Call\n}\n\n// Close is a helper method to define mock.On call\nfunc (_e *CoreSpanWriter_Expecter) Close() *CoreSpanWriter_Close_Call {\n\treturn &CoreSpanWriter_Close_Call{Call: _e.mock.On(\"Close\")}\n}\n\nfunc (_c *CoreSpanWriter_Close_Call) Run(run func()) *CoreSpanWriter_Close_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanWriter_Close_Call) Return(err error) *CoreSpanWriter_Close_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *CoreSpanWriter_Close_Call) RunAndReturn(run func() error) *CoreSpanWriter_Close_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// WriteSpan provides a mock function for the type CoreSpanWriter\nfunc (_mock *CoreSpanWriter) WriteSpan(spanStartTime time.Time, span *dbmodel.Span) {\n\t_mock.Called(spanStartTime, span)\n\treturn\n}\n\n// CoreSpanWriter_WriteSpan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteSpan'\ntype CoreSpanWriter_WriteSpan_Call struct {\n\t*mock.Call\n}\n\n// WriteSpan is a helper method to define mock.On call\n//   - spanStartTime time.Time\n//   - span *dbmodel.Span\nfunc (_e *CoreSpanWriter_Expecter) WriteSpan(spanStartTime interface{}, span interface{}) *CoreSpanWriter_WriteSpan_Call {\n\treturn &CoreSpanWriter_WriteSpan_Call{Call: _e.mock.On(\"WriteSpan\", spanStartTime, span)}\n}\n\nfunc (_c *CoreSpanWriter_WriteSpan_Call) Run(run func(spanStartTime time.Time, span *dbmodel.Span)) *CoreSpanWriter_WriteSpan_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 time.Time\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(time.Time)\n\t\t}\n\t\tvar arg1 *dbmodel.Span\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*dbmodel.Span)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreSpanWriter_WriteSpan_Call) Return() *CoreSpanWriter_WriteSpan_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *CoreSpanWriter_WriteSpan_Call) RunAndReturn(run func(spanStartTime time.Time, span *dbmodel.Span)) *CoreSpanWriter_WriteSpan_Call {\n\t_c.Run(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/reader.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.opentelemetry.io/collector/featuregate\"\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\tcfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\tesquery \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query\"\n)\n\nconst (\n\tspanIndexBaseName    = \"jaeger-span-\"\n\tserviceIndexBaseName = \"jaeger-service-\"\n\ttraceIDAggregation   = \"traceIDs\"\n\tindexPrefixSeparator = \"-\"\n\n\ttraceIDField           = \"traceID\"\n\tdurationField          = \"duration\"\n\tstartTimeField         = \"startTime\"\n\tstartTimeMillisField   = \"startTimeMillis\"\n\tserviceNameField       = \"process.serviceName\"\n\toperationNameField     = \"operationName\"\n\tobjectTagsField        = \"tag\"\n\tobjectProcessTagsField = \"process.tag\"\n\tnestedTagsField        = \"tags\"\n\tnestedProcessTagsField = \"process.tags\"\n\tnestedLogFieldsField   = \"logs.fields\"\n\ttagKeyField            = \"key\"\n\ttagValueField          = \"value\"\n\n\tdefaultNumTraces = 100\n\n\tdawnOfTimeSpanAge = time.Hour * 24 * 365 * 50\n)\n\nvar (\n\t// ErrServiceNameNotSet occurs when attempting to query with an empty service name\n\tErrServiceNameNotSet = errors.New(\"service Name must be set\")\n\n\t// ErrStartTimeMinGreaterThanMax occurs when start time min is above start time max\n\tErrStartTimeMinGreaterThanMax = errors.New(\"start Time Minimum is above Maximum\")\n\n\t// ErrDurationMinGreaterThanMax occurs when duration min is above duration max\n\tErrDurationMinGreaterThanMax = errors.New(\"duration Minimum is above Maximum\")\n\n\t// ErrMalformedRequestObject occurs when a request object is nil\n\tErrMalformedRequestObject = errors.New(\"malformed request object\")\n\n\t// ErrStartAndEndTimeNotSet occurs when start time and end time are not set\n\tErrStartAndEndTimeNotSet = errors.New(\"start and End Time must be set\")\n\n\t// ErrUnableToFindTraceIDAggregation occurs when an aggregation query for TraceIDs fail.\n\tErrUnableToFindTraceIDAggregation = errors.New(\"could not find aggregation of traceIDs\")\n\n\tdefaultMaxDuration = model.DurationAsMicroseconds(time.Hour * 24)\n\n\tobjectTagFieldList = []string{objectTagsField, objectProcessTagsField}\n\n\tnestedTagFieldList = []string{nestedTagsField, nestedProcessTagsField, nestedLogFieldsField}\n\n\t_ CoreSpanReader = (*SpanReader)(nil) // check API conformance\n\n\tdisableLegacyIDs *featuregate.Gate\n)\n\nfunc init() {\n\tdisableLegacyIDs = featuregate.GlobalRegistry().MustRegister(\n\t\t\"jaeger.es.disableLegacyId\",\n\t\tfeaturegate.StageStable, // enabled by default and cannot be disabled\n\t\tfeaturegate.WithRegisterFromVersion(\"v2.5.0\"),\n\t\tfeaturegate.WithRegisterToVersion(\"v2.8.0\"),\n\t\tfeaturegate.WithRegisterDescription(\"Legacy trace ids are the ids that used to be rendered with leading 0s omitted. Setting this gate to false will force the reader to search for the spans with trace ids having leading zeroes\"),\n\t\tfeaturegate.WithRegisterReferenceURL(\"https://github.com/jaegertracing/jaeger/issues/1578\"))\n}\n\n// SpanReader can query for and load traces from ElasticSearch\ntype SpanReader struct {\n\tclient func() es.Client\n\t// The age of the oldest service/operation we will look for. Because indices in ElasticSearch are by day,\n\t// this will be rounded down to UTC 00:00 of that day.\n\tmaxSpanAge              time.Duration\n\tserviceOperationStorage *ServiceOperationStorage\n\tspanIndexPrefix         string\n\tserviceIndexPrefix      string\n\tspanIndex               cfg.IndexOptions\n\tserviceIndex            cfg.IndexOptions\n\ttimeRangeIndices        TimeRangeIndexFn\n\tsourceFn                sourceFn\n\tmaxDocCount             int\n\tuseReadWriteAliases     bool\n\tlogger                  *zap.Logger\n\ttracer                  trace.Tracer\n\tdotReplacer             dbmodel.DotReplacer\n}\n\n// SpanReaderParams holds constructor params for NewSpanReader\ntype SpanReaderParams struct {\n\tClient              func() es.Client\n\tMaxSpanAge          time.Duration\n\tMaxDocCount         int\n\tIndexPrefix         cfg.IndexPrefix\n\tSpanIndex           cfg.IndexOptions\n\tServiceIndex        cfg.IndexOptions\n\tTagDotReplacement   string\n\tReadAliasSuffix     string\n\tUseReadWriteAliases bool\n\tRemoteReadClusters  []string\n\tSpanReadAlias       string\n\tServiceReadAlias    string\n\tLogger              *zap.Logger\n\tTracer              trace.Tracer\n}\n\n// NewSpanReader returns a new SpanReader with a metrics.\nfunc NewSpanReader(p SpanReaderParams) *SpanReader {\n\tspanIndexPrefix := p.SpanReadAlias\n\tserviceIndexPrefix := p.ServiceReadAlias\n\n\tif spanIndexPrefix == \"\" {\n\t\tspanIndexPrefix = p.IndexPrefix.Apply(spanIndexBaseName)\n\t}\n\tif serviceIndexPrefix == \"\" {\n\t\tserviceIndexPrefix = p.IndexPrefix.Apply(serviceIndexBaseName)\n\t}\n\n\tmaxSpanAge := p.MaxSpanAge\n\t// Setting the maxSpanAge to a large duration will ensure all spans in the \"read\" alias are accessible by queries (query window = [now - maxSpanAge, now]).\n\tif p.UseReadWriteAliases {\n\t\tmaxSpanAge = dawnOfTimeSpanAge\n\t}\n\n\tvar timeRangeFn TimeRangeIndexFn\n\tif p.SpanReadAlias != \"\" && p.ServiceReadAlias != \"\" {\n\t\t// When using explicit aliases, return them directly without any date logic\n\t\ttimeRangeFn = func(indexPrefix string, _ string, _ time.Time, _ time.Time, _ time.Duration) []string {\n\t\t\treturn []string{indexPrefix}\n\t\t}\n\t} else {\n\t\ttimeRangeFn = TimeRangeIndicesFn(p.UseReadWriteAliases, p.ReadAliasSuffix, p.RemoteReadClusters)\n\t}\n\n\treturn &SpanReader{\n\t\tclient:                  p.Client,\n\t\tmaxSpanAge:              maxSpanAge,\n\t\tserviceOperationStorage: NewServiceOperationStorage(p.Client, p.Logger, 0), // the decorator takes care of metrics\n\t\tspanIndexPrefix:         spanIndexPrefix,\n\t\tserviceIndexPrefix:      serviceIndexPrefix,\n\t\tspanIndex:               p.SpanIndex,\n\t\tserviceIndex:            p.ServiceIndex,\n\t\ttimeRangeIndices:        LoggingTimeRangeIndexFn(p.Logger, timeRangeFn),\n\t\tsourceFn:                getSourceFn(p.MaxDocCount),\n\t\tmaxDocCount:             p.MaxDocCount,\n\t\tuseReadWriteAliases:     p.UseReadWriteAliases,\n\t\tlogger:                  p.Logger,\n\t\ttracer:                  p.Tracer,\n\t\tdotReplacer:             dbmodel.NewDotReplacer(p.TagDotReplacement),\n\t}\n}\n\ntype TimeRangeIndexFn func(indexName string, indexDateLayout string, startTime time.Time, endTime time.Time, reduceDuration time.Duration) []string\n\ntype sourceFn func(query elastic.Query, nextTime uint64) *elastic.SearchSource\n\nfunc LoggingTimeRangeIndexFn(logger *zap.Logger, fn TimeRangeIndexFn) TimeRangeIndexFn {\n\tif !logger.Core().Enabled(zap.DebugLevel) {\n\t\treturn fn\n\t}\n\treturn func(indexName string, indexDateLayout string, startTime time.Time, endTime time.Time, reduceDuration time.Duration) []string {\n\t\tindices := fn(indexName, indexDateLayout, startTime, endTime, reduceDuration)\n\t\tlogger.Debug(\"Reading from ES indices\", zap.Strings(\"index\", indices))\n\t\treturn indices\n\t}\n}\n\nfunc TimeRangeIndicesFn(useReadWriteAliases bool, readAliasSuffix string, remoteReadClusters []string) TimeRangeIndexFn {\n\tsuffix := \"\"\n\tif useReadWriteAliases {\n\t\tif readAliasSuffix != \"\" {\n\t\t\tsuffix = readAliasSuffix\n\t\t} else {\n\t\t\tsuffix = \"read\"\n\t\t}\n\t}\n\treturn addRemoteReadClusters(\n\t\tgetTimeRangeIndexFn(useReadWriteAliases, suffix),\n\t\tremoteReadClusters,\n\t)\n}\n\nfunc getTimeRangeIndexFn(useReadWriteAliases bool, readAlias string) TimeRangeIndexFn {\n\tif useReadWriteAliases {\n\t\treturn func(indexPrefix, _ /* indexDateLayout */ string, _ /* startTime */ time.Time, _ /* endTime */ time.Time, _ /* reduceDuration */ time.Duration) []string {\n\t\t\treturn []string{indexPrefix + readAlias}\n\t\t}\n\t}\n\treturn timeRangeIndices\n}\n\n// Add a remote cluster prefix for each cluster and for each index and add it to the list of original indices.\n// Elasticsearch cross cluster api example GET /twitter,cluster_one:twitter,cluster_two:twitter/_search.\nfunc addRemoteReadClusters(fn TimeRangeIndexFn, remoteReadClusters []string) TimeRangeIndexFn {\n\treturn func(indexPrefix string, indexDateLayout string, startTime time.Time, endTime time.Time, reduceDuration time.Duration) []string {\n\t\tjaegerIndices := fn(indexPrefix, indexDateLayout, startTime, endTime, reduceDuration)\n\t\tif len(remoteReadClusters) == 0 {\n\t\t\treturn jaegerIndices\n\t\t}\n\n\t\tfor _, jaegerIndex := range jaegerIndices {\n\t\t\tfor _, remoteCluster := range remoteReadClusters {\n\t\t\t\tremoteIndex := remoteCluster + \":\" + jaegerIndex\n\t\t\t\tjaegerIndices = append(jaegerIndices, remoteIndex)\n\t\t\t}\n\t\t}\n\n\t\treturn jaegerIndices\n\t}\n}\n\nfunc getSourceFn(maxDocCount int) sourceFn {\n\treturn func(query elastic.Query, nextTime uint64) *elastic.SearchSource {\n\t\treturn elastic.NewSearchSource().\n\t\t\tQuery(query).\n\t\t\tSize(maxDocCount).\n\t\t\tSort(\"startTime\", true).\n\t\t\tSearchAfter(nextTime)\n\t}\n}\n\n// timeRangeIndices returns the array of indices that we need to query, based on query params\nfunc timeRangeIndices(indexName, indexDateLayout string, startTime time.Time, endTime time.Time, reduceDuration time.Duration) []string {\n\tvar indices []string\n\tfirstIndex := indexWithDate(indexName, indexDateLayout, startTime)\n\tcurrentIndex := indexWithDate(indexName, indexDateLayout, endTime)\n\tfor currentIndex != firstIndex && endTime.After(startTime) {\n\t\tif len(indices) == 0 || indices[len(indices)-1] != currentIndex {\n\t\t\tindices = append(indices, currentIndex)\n\t\t}\n\t\tendTime = endTime.Add(reduceDuration)\n\t\tcurrentIndex = indexWithDate(indexName, indexDateLayout, endTime)\n\t}\n\tindices = append(indices, firstIndex)\n\treturn indices\n}\n\n// GetTraces takes a traceID and returns a Trace associated with that traceID\nfunc (s *SpanReader) GetTraces(ctx context.Context, query []dbmodel.TraceID) ([]dbmodel.Trace, error) {\n\tctx, span := s.tracer.Start(ctx, \"GetTrace\")\n\tdefer span.End()\n\tcurrentTime := time.Now()\n\t// TODO: use start time & end time in \"query\" struct\n\treturn s.multiRead(ctx, query, currentTime.Add(-s.maxSpanAge), currentTime)\n}\n\nfunc (s *SpanReader) collectSpans(esSpansRaw []*elastic.SearchHit) ([]dbmodel.Span, error) {\n\tspans := make([]dbmodel.Span, len(esSpansRaw))\n\n\tfor i, esSpanRaw := range esSpansRaw {\n\t\tdbSpan, err := s.unmarshalJSONSpan(esSpanRaw)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"marshalling JSON to span object failed: %w\", err)\n\t\t}\n\t\ts.mergeAllNestedAndElevatedTagsOfSpan(&dbSpan)\n\t\tspans[i] = dbSpan\n\t}\n\treturn spans, nil\n}\n\nfunc (*SpanReader) unmarshalJSONSpan(esSpanRaw *elastic.SearchHit) (dbmodel.Span, error) {\n\tesSpanInByteArray := esSpanRaw.Source\n\n\tvar jsonSpan dbmodel.Span\n\n\td := json.NewDecoder(bytes.NewReader(esSpanInByteArray))\n\td.UseNumber()\n\tif err := d.Decode(&jsonSpan); err != nil {\n\t\treturn dbmodel.Span{}, err\n\t}\n\treturn jsonSpan, nil\n}\n\n// GetServices returns all services traced by Jaeger, ordered by frequency\nfunc (s *SpanReader) GetServices(ctx context.Context) ([]string, error) {\n\tctx, span := s.tracer.Start(ctx, \"GetService\")\n\tdefer span.End()\n\tcurrentTime := time.Now()\n\tjaegerIndices := s.timeRangeIndices(\n\t\ts.serviceIndexPrefix,\n\t\ts.serviceIndex.DateLayout,\n\t\tcurrentTime.Add(-s.maxSpanAge),\n\t\tcurrentTime,\n\t\tcfg.RolloverFrequencyAsNegativeDuration(s.serviceIndex.RolloverFrequency),\n\t)\n\treturn s.serviceOperationStorage.getServices(ctx, jaegerIndices, s.maxDocCount)\n}\n\n// GetOperations returns all operations for a specific service traced by Jaeger\nfunc (s *SpanReader) GetOperations(\n\tctx context.Context,\n\tquery dbmodel.OperationQueryParameters,\n) ([]dbmodel.Operation, error) {\n\tctx, span := s.tracer.Start(ctx, \"GetOperations\")\n\tdefer span.End()\n\tcurrentTime := time.Now()\n\tjaegerIndices := s.timeRangeIndices(\n\t\ts.serviceIndexPrefix,\n\t\ts.serviceIndex.DateLayout,\n\t\tcurrentTime.Add(-s.maxSpanAge),\n\t\tcurrentTime,\n\t\tcfg.RolloverFrequencyAsNegativeDuration(s.serviceIndex.RolloverFrequency),\n\t)\n\toperations, err := s.serviceOperationStorage.getOperations(ctx, jaegerIndices, query.ServiceName, s.maxDocCount)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// TODO: https://github.com/jaegertracing/jaeger/issues/1923\n\t// \t- return the operations with actual span kind that meet requirement\n\tvar result []dbmodel.Operation\n\tfor _, operation := range operations {\n\t\tresult = append(result, dbmodel.Operation{\n\t\t\tName: operation,\n\t\t})\n\t}\n\treturn result, err\n}\n\nfunc bucketToStringArray[T ~string](buckets []*elastic.AggregationBucketKeyItem) ([]T, error) {\n\tstringSlice := make([]T, len(buckets))\n\tfor i, keyitem := range buckets {\n\t\tstr, ok := keyitem.Key.(string)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"non-string key found in aggregation\")\n\t\t}\n\t\tstringSlice[i] = T(str)\n\t}\n\treturn stringSlice, nil\n}\n\n// FindTraces retrieves traces that match the traceQuery\nfunc (s *SpanReader) FindTraces(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.Trace, error) {\n\tctx, span := s.tracer.Start(ctx, \"FindTraces\")\n\tdefer span.End()\n\n\tuniqueTraceIDs, err := s.FindTraceIDs(ctx, traceQuery)\n\tif err != nil {\n\t\treturn nil, es.DetailedError(err)\n\t}\n\treturn s.multiRead(ctx, uniqueTraceIDs, traceQuery.StartTimeMin, traceQuery.StartTimeMax)\n}\n\n// FindTraceIDs retrieves traces IDs that match the traceQuery\nfunc (s *SpanReader) FindTraceIDs(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.TraceID, error) {\n\tctx, span := s.tracer.Start(ctx, \"FindTraceIDs\")\n\tdefer span.End()\n\n\tif err := validateQuery(traceQuery); err != nil {\n\t\treturn nil, err\n\t}\n\tif traceQuery.NumTraces == 0 {\n\t\ttraceQuery.NumTraces = defaultNumTraces\n\t}\n\n\tesTraceIDs, err := s.findTraceIDsFromQuery(ctx, traceQuery)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn esTraceIDs, nil\n}\n\nfunc (s *SpanReader) multiRead(ctx context.Context, traceIDs []dbmodel.TraceID, startTime, endTime time.Time) ([]dbmodel.Trace, error) {\n\tctx, childSpan := s.tracer.Start(ctx, \"multiRead\")\n\tdefer childSpan.End()\n\n\tif childSpan.IsRecording() {\n\t\ttracesIDs := make([]string, len(traceIDs))\n\t\tfor i, traceID := range traceIDs {\n\t\t\ttracesIDs[i] = string(traceID)\n\t\t}\n\t\tchildSpan.SetAttributes(attribute.Key(\"trace_ids\").StringSlice(tracesIDs))\n\t}\n\n\ttraces := make([]dbmodel.Trace, 0, len(traceIDs))\n\n\tif len(traceIDs) == 0 {\n\t\treturn traces, nil\n\t}\n\n\t// Add an hour in both directions so that traces that straddle two indexes are retrieved.\n\t// i.e starts in one and ends in another.\n\tindices := s.timeRangeIndices(\n\t\ts.spanIndexPrefix,\n\t\ts.spanIndex.DateLayout,\n\t\tstartTime.Add(-time.Hour),\n\t\tendTime.Add(time.Hour),\n\t\tcfg.RolloverFrequencyAsNegativeDuration(s.spanIndex.RolloverFrequency),\n\t)\n\tnextTime := model.TimeAsEpochMicroseconds(startTime.Add(-time.Hour))\n\tsearchAfterTime := make(map[dbmodel.TraceID]uint64)\n\ttotalDocumentsFetched := make(map[dbmodel.TraceID]int)\n\ttracesMap := make(map[dbmodel.TraceID]*dbmodel.Trace)\n\tfor len(traceIDs) != 0 {\n\t\tsearchRequests := make([]*elastic.SearchRequest, len(traceIDs))\n\t\tfor i, traceID := range traceIDs {\n\t\t\ttraceQuery := buildTraceByIDQuery(traceID)\n\t\t\tquery := elastic.NewBoolQuery().\n\t\t\t\tMust(traceQuery)\n\t\t\tif s.useReadWriteAliases {\n\t\t\t\tstartTimeRangeQuery := s.buildStartTimeQuery(startTime.Add(-time.Hour*24), endTime.Add(time.Hour*24))\n\t\t\t\tquery = query.Must(startTimeRangeQuery)\n\t\t\t}\n\n\t\t\tif val, ok := searchAfterTime[traceID]; ok {\n\t\t\t\tnextTime = val\n\t\t\t}\n\n\t\t\ts := s.sourceFn(query, nextTime).\n\t\t\t\tTrackTotalHits(true)\n\t\t\tsearchRequests[i] = elastic.NewSearchRequest().\n\t\t\t\tIgnoreUnavailable(true).\n\t\t\t\tSource(s)\n\t\t}\n\t\t// set traceIDs to empty\n\t\ttraceIDs = nil\n\t\tresults, err := s.client().MultiSearch().Add(searchRequests...).Index(indices...).Do(ctx)\n\t\tif err != nil {\n\t\t\terr = es.DetailedError(err)\n\t\t\tlogErrorToSpan(childSpan, err)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(results.Responses) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, result := range results.Responses {\n\t\t\tif result.Hits == nil || len(result.Hits.Hits) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tspans, err := s.collectSpans(result.Hits.Hits)\n\t\t\tif err != nil {\n\t\t\t\terr = es.DetailedError(err)\n\t\t\t\tlogErrorToSpan(childSpan, err)\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tlastSpan := spans[len(spans)-1]\n\n\t\t\tif traceSpan, ok := tracesMap[lastSpan.TraceID]; ok {\n\t\t\t\ttraceSpan.Spans = append(traceSpan.Spans, spans...)\n\t\t\t} else {\n\t\t\t\ttraces = append(traces, dbmodel.Trace{Spans: spans})\n\t\t\t\ttracesMap[lastSpan.TraceID] = &traces[len(traces)-1]\n\t\t\t}\n\n\t\t\ttotalDocumentsFetched[lastSpan.TraceID] += len(result.Hits.Hits)\n\t\t\tif totalDocumentsFetched[lastSpan.TraceID] < int(result.TotalHits()) {\n\t\t\t\ttraceIDs = append(traceIDs, lastSpan.TraceID)\n\t\t\t\tsearchAfterTime[lastSpan.TraceID] = lastSpan.StartTime\n\t\t\t}\n\t\t}\n\t}\n\treturn traces, nil\n}\n\nfunc buildTraceByIDQuery(traceID dbmodel.TraceID) elastic.Query {\n\ttraceIDStr := string(traceID)\n\tif traceIDStr[0] != '0' || disableLegacyIDs.IsEnabled() {\n\t\treturn elastic.NewTermQuery(traceIDField, traceIDStr)\n\t}\n\t// https://github.com/jaegertracing/jaeger/pull/1956 added leading zeros to IDs\n\t// So we need to also read IDs without leading zeros for compatibility with previously saved data.\n\tlegacyTraceID := strings.TrimLeft(traceIDStr, \"0\")\n\treturn elastic.NewBoolQuery().Should(\n\t\telastic.NewTermQuery(traceIDField, traceIDStr).Boost(2),\n\t\telastic.NewTermQuery(traceIDField, legacyTraceID))\n}\n\nfunc validateQuery(p dbmodel.TraceQueryParameters) error {\n\tif p.ServiceName == \"\" && len(p.Tags) > 0 {\n\t\treturn ErrServiceNameNotSet\n\t}\n\tif p.StartTimeMin.IsZero() || p.StartTimeMax.IsZero() {\n\t\treturn ErrStartAndEndTimeNotSet\n\t}\n\tif p.StartTimeMax.Before(p.StartTimeMin) {\n\t\treturn ErrStartTimeMinGreaterThanMax\n\t}\n\tif p.DurationMin != 0 && p.DurationMax != 0 && p.DurationMin > p.DurationMax {\n\t\treturn ErrDurationMinGreaterThanMax\n\t}\n\treturn nil\n}\n\nfunc (s *SpanReader) findTraceIDsFromQuery(ctx context.Context, traceQuery dbmodel.TraceQueryParameters) ([]dbmodel.TraceID, error) {\n\tctx, childSpan := s.tracer.Start(ctx, \"findTraceIDs\")\n\tdefer childSpan.End()\n\t//  Below is the JSON body to our HTTP GET request to ElasticSearch. This function creates this.\n\t// {\n\t//      \"size\": 0,\n\t//      \"query\": {\n\t//        \"bool\": {\n\t//          \"must\": [\n\t//            { \"match\": { \"operationName\":   \"op1\"      }},\n\t//            { \"match\": { \"process.serviceName\": \"service1\" }},\n\t//            { \"range\":  { \"startTime\": { \"gte\": 0, \"lte\": 90000000000000000 }}},\n\t//            { \"range\":  { \"duration\": { \"gte\": 0, \"lte\": 90000000000000000 }}},\n\t//            { \"should\": [\n\t//                   { \"nested\" : {\n\t//                      \"path\" : \"tags\",\n\t//                      \"query\" : {\n\t//                          \"bool\" : {\n\t//                              \"must\" : [\n\t//                              { \"match\" : {\"tags.key\" : \"tag3\"} },\n\t//                              { \"match\" : {\"tags.value\" : \"xyz\"} }\n\t//                              ]\n\t//                          }}}},\n\t//                   { \"nested\" : {\n\t//                          \"path\" : \"process.tags\",\n\t//                          \"query\" : {\n\t//                              \"bool\" : {\n\t//                                  \"must\" : [\n\t//                                  { \"match\" : {\"tags.key\" : \"tag3\"} },\n\t//                                  { \"match\" : {\"tags.value\" : \"xyz\"} }\n\t//                                  ]\n\t//                              }}}},\n\t//                   { \"nested\" : {\n\t//                          \"path\" : \"logs.fields\",\n\t//                          \"query\" : {\n\t//                              \"bool\" : {\n\t//                                  \"must\" : [\n\t//                                  { \"match\" : {\"tags.key\" : \"tag3\"} },\n\t//                                  { \"match\" : {\"tags.value\" : \"xyz\"} }\n\t//                                  ]\n\t//                              }}}},\n\t//                   { \"bool\":{\n\t//                           \"must\": {\n\t//                               \"match\":{ \"tags.bat\":{ \"query\":\"spook\" }}\n\t//                           }}},\n\t//                   { \"bool\":{\n\t//                           \"must\": {\n\t//                               \"match\":{ \"tag.bat\":{ \"query\":\"spook\" }}\n\t//                           }}}\n\t//                ]\n\t//              }\n\t//          ]\n\t//        }\n\t//      },\n\t//      \"aggs\": { \"traceIDs\" : { \"terms\" : {\"size\": 100,\"field\": \"traceID\" }}}\n\t//  }\n\taggregation := s.buildTraceIDAggregation(traceQuery.NumTraces)\n\tboolQuery := s.buildFindTraceIDsQuery(traceQuery)\n\tjaegerIndices := s.timeRangeIndices(\n\t\ts.spanIndexPrefix,\n\t\ts.spanIndex.DateLayout,\n\t\ttraceQuery.StartTimeMin,\n\t\ttraceQuery.StartTimeMax,\n\t\tcfg.RolloverFrequencyAsNegativeDuration(s.spanIndex.RolloverFrequency),\n\t)\n\n\tsearchService := s.client().Search(jaegerIndices...).\n\t\tSize(0). // set to 0 because we don't want actual documents.\n\t\tAggregation(traceIDAggregation, aggregation).\n\t\tIgnoreUnavailable(true).\n\t\tQuery(boolQuery)\n\n\tsearchResult, err := searchService.Do(ctx)\n\tif err != nil {\n\t\terr = es.DetailedError(err)\n\t\ts.logger.Info(\"es search services failed\", zap.Any(\"traceQuery\", traceQuery), zap.Error(err))\n\t\treturn nil, fmt.Errorf(\"search services failed: %w\", err)\n\t}\n\tif searchResult.Aggregations == nil {\n\t\treturn []dbmodel.TraceID{}, nil\n\t}\n\tbucket, found := searchResult.Aggregations.Terms(traceIDAggregation)\n\tif !found {\n\t\treturn nil, ErrUnableToFindTraceIDAggregation\n\t}\n\n\ttraceIDBuckets := bucket.Buckets\n\treturn bucketToStringArray[dbmodel.TraceID](traceIDBuckets)\n}\n\nfunc (s *SpanReader) buildTraceIDAggregation(numOfTraces int) elastic.Aggregation {\n\treturn elastic.NewTermsAggregation().\n\t\tSize(numOfTraces).\n\t\tField(traceIDField).\n\t\tOrder(startTimeField, false).\n\t\tSubAggregation(startTimeField, s.buildTraceIDSubAggregation())\n}\n\nfunc (*SpanReader) buildTraceIDSubAggregation() elastic.Aggregation {\n\treturn elastic.NewMaxAggregation().\n\t\tField(startTimeField)\n}\n\nfunc (s *SpanReader) buildFindTraceIDsQuery(traceQuery dbmodel.TraceQueryParameters) elastic.Query {\n\tboolQuery := elastic.NewBoolQuery()\n\n\t// add duration query\n\tif traceQuery.DurationMax != 0 || traceQuery.DurationMin != 0 {\n\t\tdurationQuery := s.buildDurationQuery(traceQuery.DurationMin, traceQuery.DurationMax)\n\t\tboolQuery.Must(durationQuery)\n\t}\n\n\t// add startTime query\n\tstartTimeQuery := s.buildStartTimeQuery(traceQuery.StartTimeMin, traceQuery.StartTimeMax)\n\tboolQuery.Must(startTimeQuery)\n\n\t// add process.serviceName query\n\tif traceQuery.ServiceName != \"\" {\n\t\tserviceNameQuery := s.buildServiceNameQuery(traceQuery.ServiceName)\n\t\tboolQuery.Must(serviceNameQuery)\n\t}\n\n\t// add operationName query\n\tif traceQuery.OperationName != \"\" {\n\t\toperationNameQuery := s.buildOperationNameQuery(traceQuery.OperationName)\n\t\tboolQuery.Must(operationNameQuery)\n\t}\n\n\tfor k, v := range traceQuery.Tags {\n\t\ttagQuery := s.buildTagQuery(k, v)\n\t\tboolQuery.Must(tagQuery)\n\t}\n\treturn boolQuery\n}\n\nfunc (*SpanReader) buildDurationQuery(durationMin time.Duration, durationMax time.Duration) elastic.Query {\n\tminDurationMicros := model.DurationAsMicroseconds(durationMin)\n\tmaxDurationMicros := defaultMaxDuration\n\tif durationMax != 0 {\n\t\tmaxDurationMicros = model.DurationAsMicroseconds(durationMax)\n\t}\n\treturn esquery.NewRangeQuery(durationField).Gte(minDurationMicros).Lte(maxDurationMicros)\n}\n\nfunc (*SpanReader) buildStartTimeQuery(startTimeMin time.Time, startTimeMax time.Time) elastic.Query {\n\tminStartTimeMicros := model.TimeAsEpochMicroseconds(startTimeMin)\n\tmaxStartTimeMicros := model.TimeAsEpochMicroseconds(startTimeMax)\n\t// startTimeMillisField is date field in ES mapping.\n\t// Using date field in range queries helps to skip search on unnecessary shards at Elasticsearch side.\n\t// https://discuss.elastic.co/t/timeline-query-on-timestamped-indices/129328/2\n\treturn esquery.NewRangeQuery(startTimeMillisField).Gte(minStartTimeMicros / 1000).Lte(maxStartTimeMicros / 1000)\n}\n\nfunc (*SpanReader) buildServiceNameQuery(serviceName string) elastic.Query {\n\treturn elastic.NewMatchQuery(serviceNameField, serviceName)\n}\n\nfunc (*SpanReader) buildOperationNameQuery(operationName string) elastic.Query {\n\treturn elastic.NewMatchQuery(operationNameField, operationName)\n}\n\nfunc (s *SpanReader) buildTagQuery(k string, v string) elastic.Query {\n\tobjectTagListLen := len(objectTagFieldList)\n\tqueries := make([]elastic.Query, len(nestedTagFieldList)+objectTagListLen)\n\tkd := s.dotReplacer.ReplaceDot(k)\n\tfor i := range objectTagFieldList {\n\t\tqueries[i] = s.buildObjectQuery(objectTagFieldList[i], kd, v)\n\t}\n\tfor i := range nestedTagFieldList {\n\t\tqueries[i+objectTagListLen] = s.buildNestedQuery(nestedTagFieldList[i], k, v)\n\t}\n\n\t// but configuration can change over time\n\treturn elastic.NewBoolQuery().Should(queries...)\n}\n\nfunc (*SpanReader) buildNestedQuery(field string, k string, v string) elastic.Query {\n\tkeyField := fmt.Sprintf(\"%s.%s\", field, tagKeyField)\n\tvalueField := fmt.Sprintf(\"%s.%s\", field, tagValueField)\n\tkeyQuery := elastic.NewMatchQuery(keyField, k)\n\tvalueQuery := elastic.NewRegexpQuery(valueField, v)\n\ttagBoolQuery := elastic.NewBoolQuery().Must(keyQuery, valueQuery)\n\treturn elastic.NewNestedQuery(field, tagBoolQuery)\n}\n\nfunc (*SpanReader) buildObjectQuery(field string, k string, v string) elastic.Query {\n\tkeyField := fmt.Sprintf(\"%s.%s\", field, k)\n\tkeyQuery := elastic.NewRegexpQuery(keyField, v)\n\treturn elastic.NewBoolQuery().Must(keyQuery)\n}\n\nfunc (s *SpanReader) mergeAllNestedAndElevatedTagsOfSpan(span *dbmodel.Span) {\n\tprocessTags := s.mergeNestedAndElevatedTags(span.Process.Tags, span.Process.Tag)\n\tspan.Process.Tags = processTags\n\tspanTags := s.mergeNestedAndElevatedTags(span.Tags, span.Tag)\n\tspan.Tags = spanTags\n}\n\nfunc (s *SpanReader) mergeNestedAndElevatedTags(nestedTags []dbmodel.KeyValue, elevatedTags map[string]any) []dbmodel.KeyValue {\n\tmergedTags := make([]dbmodel.KeyValue, 0, len(nestedTags)+len(elevatedTags))\n\tmergedTags = append(mergedTags, nestedTags...)\n\tfor k, v := range elevatedTags {\n\t\tkv := s.convertTagField(k, v)\n\t\tmergedTags = append(mergedTags, kv)\n\t\tdelete(elevatedTags, k)\n\t}\n\treturn mergedTags\n}\n\nfunc (s *SpanReader) convertTagField(k string, v any) dbmodel.KeyValue {\n\tdKey := s.dotReplacer.ReplaceDotReplacement(k)\n\tkv := dbmodel.KeyValue{\n\t\tKey:   dKey,\n\t\tValue: v,\n\t}\n\tswitch val := v.(type) {\n\tcase int64:\n\t\tkv.Type = dbmodel.Int64Type\n\tcase float64:\n\t\tkv.Type = dbmodel.Float64Type\n\tcase bool:\n\t\tkv.Type = dbmodel.BoolType\n\tcase string:\n\t\tkv.Type = dbmodel.StringType\n\t// the binary is never returned, ES returns it as string with base64 encoding\n\tcase []byte:\n\t\tkv.Type = dbmodel.BinaryType\n\t// in spans are decoded using json.UseNumber() to preserve the type\n\t// however note that float(1) will be parsed as int as ES does not store decimal point\n\tcase json.Number:\n\t\tn, err := val.Int64()\n\t\tif err == nil {\n\t\t\tkv.Value = n\n\t\t\tkv.Type = dbmodel.Int64Type\n\t\t} else {\n\t\t\tf, err := val.Float64()\n\t\t\tif err != nil {\n\t\t\t\treturn dbmodel.KeyValue{\n\t\t\t\t\tKey:   dKey,\n\t\t\t\t\tValue: fmt.Sprintf(\"invalid tag type in %+v: %s\", v, err.Error()),\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t}\n\t\t\t}\n\t\t\tkv.Value = f\n\t\t\tkv.Type = dbmodel.Float64Type\n\t\t}\n\tdefault:\n\t\treturn dbmodel.KeyValue{\n\t\t\tKey:   dKey,\n\t\t\tValue: fmt.Sprintf(\"invalid tag type in %+v\", v),\n\t\t\tType:  dbmodel.StringType,\n\t\t}\n\t}\n\treturn kv\n}\n\nfunc logErrorToSpan(span trace.Span, err error) {\n\tspan.RecordError(err)\n\tspan.SetStatus(codes.Error, err.Error())\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/reader_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"maps\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/sdk/trace/tracetest\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst (\n\tdefaultMaxDocCount = 10_000\n\ttestingTraceId     = \"testing-id\"\n)\n\nvar exampleESSpan = []byte(\n\t`{\n\t   \"traceID\": \"1\",\n\t   \"parentSpanID\": \"2\",\n\t   \"spanID\": \"3\",\n\t   \"flags\": 0,\n\t   \"operationName\": \"op\",\n\t   \"references\": [],\n\t   \"startTime\": 812965625,\n\t   \"duration\": 3290114992,\n\t   \"tags\": [\n\t      {\n\t\t \"key\": \"tag\",\n\t\t \"value\": \"1965806585\",\n\t\t \"type\": \"int64\"\n\t      }\n\t   ],\n\t   \"logs\": [\n\t      {\n\t\t \"timestamp\": 812966073,\n\t\t \"fields\": [\n\t\t    {\n\t\t       \"key\": \"logtag\",\n\t\t       \"value\": \"helloworld\",\n\t\t       \"type\": \"string\"\n\t\t    }\n\t\t ]\n\t      }\n\t   ],\n\t   \"process\": {\n\t      \"serviceName\": \"serv\",\n\t      \"tags\": [\n\t\t {\n\t\t    \"key\": \"processtag\",\n\t\t    \"value\": \"false\",\n\t\t    \"type\": \"bool\"\n\t\t }\n\t      ]\n\t   }\n\t}`)\n\ntype spanReaderTest struct {\n\tclient      *mocks.Client\n\tlogger      *zap.Logger\n\tlogBuffer   *testutils.Buffer\n\ttraceBuffer *tracetest.InMemoryExporter\n\treader      *SpanReader\n}\n\nfunc tracerProvider(t *testing.T) (trace.TracerProvider, *tracetest.InMemoryExporter, func()) {\n\texporter := tracetest.NewInMemoryExporter()\n\ttp := sdktrace.NewTracerProvider(\n\t\tsdktrace.WithSampler(sdktrace.AlwaysSample()),\n\t\tsdktrace.WithSyncer(exporter),\n\t)\n\tcloser := func() {\n\t\trequire.NoError(t, tp.Shutdown(context.Background()))\n\t}\n\treturn tp, exporter, closer\n}\n\nfunc withSpanReader(t *testing.T, fn func(r *spanReaderTest)) {\n\tclient := &mocks.Client{}\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\tlogger, logBuffer := testutils.NewLogger()\n\tr := &spanReaderTest{\n\t\tclient:      client,\n\t\tlogger:      logger,\n\t\tlogBuffer:   logBuffer,\n\t\ttraceBuffer: exp,\n\t\treader: NewSpanReader(SpanReaderParams{\n\t\t\tClient:            func() es.Client { return client },\n\t\t\tLogger:            zap.NewNop(),\n\t\t\tTracer:            tracer.Tracer(\"test\"),\n\t\t\tMaxSpanAge:        0,\n\t\t\tTagDotReplacement: \"@\",\n\t\t\tMaxDocCount:       defaultMaxDocCount,\n\t\t}),\n\t}\n\tfn(r)\n}\n\nfunc withArchiveSpanReader(t *testing.T, readAlias bool, readAliasSuffix string, fn func(r *spanReaderTest)) {\n\tclient := &mocks.Client{}\n\ttracer, exp, closer := tracerProvider(t)\n\tdefer closer()\n\tlogger, logBuffer := testutils.NewLogger()\n\tr := &spanReaderTest{\n\t\tclient:      client,\n\t\tlogger:      logger,\n\t\tlogBuffer:   logBuffer,\n\t\ttraceBuffer: exp,\n\t\treader: NewSpanReader(SpanReaderParams{\n\t\t\tClient:              func() es.Client { return client },\n\t\t\tLogger:              zap.NewNop(),\n\t\t\tTracer:              tracer.Tracer(\"test\"),\n\t\t\tMaxSpanAge:          0,\n\t\t\tTagDotReplacement:   \"@\",\n\t\t\tReadAliasSuffix:     readAliasSuffix,\n\t\t\tUseReadWriteAliases: readAlias,\n\t\t}),\n\t}\n\tfn(r)\n}\n\nfunc TestNewSpanReader(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tparams     SpanReaderParams\n\t\tmaxSpanAge time.Duration\n\t}{\n\t\t{\n\t\t\tname: \"no rollover\",\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tMaxSpanAge: time.Hour * 72,\n\t\t\t},\n\t\t\tmaxSpanAge: time.Hour * 72,\n\t\t},\n\t\t{\n\t\t\tname: \"rollover enabled\",\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tMaxSpanAge:          time.Hour * 72,\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t},\n\t\t\tmaxSpanAge: time.Hour * 24 * 365 * 50,\n\t\t},\n\t\t{\n\t\t\tname: \"explicit read aliases with UseReadWriteAliases\",\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tMaxSpanAge:          time.Hour * 72,\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanReadAlias:       \"production-traces-read\",\n\t\t\t\tServiceReadAlias:    \"production-services-read\",\n\t\t\t},\n\t\t\tmaxSpanAge: time.Hour * 24 * 365 * 50,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tparams := test.params\n\t\t\tparams.Logger = zaptest.NewLogger(t)\n\t\t\treader := NewSpanReader(params)\n\t\t\trequire.NotNil(t, reader)\n\t\t\tassert.Equal(t, test.maxSpanAge, reader.maxSpanAge)\n\t\t})\n\t}\n}\n\nfunc TestSpanReaderIndices(t *testing.T) {\n\tclient := &mocks.Client{}\n\tclientFn := func() es.Client { return client }\n\tdate := time.Date(2019, 10, 10, 5, 0, 0, 0, time.UTC)\n\n\tspanDataLayout := \"2006-01-02-15\"\n\tserviceDataLayout := \"2006-01-02\"\n\tspanDataLayoutFormat := date.UTC().Format(spanDataLayout)\n\tserviceDataLayoutFormat := date.UTC().Format(serviceDataLayout)\n\n\tlogger, _ := testutils.NewLogger()\n\ttracer, _, closer := tracerProvider(t)\n\tdefer closer()\n\n\tspanIndexOpts := config.IndexOptions{DateLayout: spanDataLayout}\n\tserviceIndexOpts := config.IndexOptions{DateLayout: serviceDataLayout}\n\n\ttestCases := []struct {\n\t\tindices []string\n\t\tparams  SpanReaderParams\n\t}{\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex:    spanIndexOpts,\n\t\t\t\tServiceIndex: serviceIndexOpts,\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + spanDataLayoutFormat, serviceIndexBaseName + serviceDataLayoutFormat},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + \"read\", serviceIndexBaseName + \"read\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tReadAliasSuffix: \"archive\", // ignored because ReadWriteAliases is false\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName, serviceIndexBaseName},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex:    spanIndexOpts,\n\t\t\t\tServiceIndex: serviceIndexOpts,\n\t\t\t\tIndexPrefix:  \"foo:\",\n\t\t\t},\n\t\t\tindices: []string{\"foo:\" + config.IndexPrefixSeparator + spanIndexBaseName + spanDataLayoutFormat, \"foo:\" + config.IndexPrefixSeparator + serviceIndexBaseName + serviceDataLayoutFormat},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, IndexPrefix: \"foo:\", UseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{\"foo:-\" + spanIndexBaseName + \"read\", \"foo:-\" + serviceIndexBaseName + \"read\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tReadAliasSuffix:     \"archive\",\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + \"archive\", serviceIndexBaseName + \"archive\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, IndexPrefix: \"foo:\", UseReadWriteAliases: true, ReadAliasSuffix: \"archive\",\n\t\t\t},\n\t\t\tindices: []string{\"foo:\" + config.IndexPrefixSeparator + spanIndexBaseName + \"archive\", \"foo:\" + config.IndexPrefixSeparator + serviceIndexBaseName + \"archive\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex:        spanIndexOpts,\n\t\t\t\tServiceIndex:     serviceIndexOpts,\n\t\t\t\tSpanReadAlias:    \"custom-span-read-alias\",\n\t\t\t\tServiceReadAlias: \"custom-service-read-alias\",\n\t\t\t},\n\t\t\tindices: []string{\"custom-span-read-alias\", \"custom-service-read-alias\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex:           spanIndexOpts,\n\t\t\t\tServiceIndex:        serviceIndexOpts,\n\t\t\t\tIndexPrefix:         \"foo:\",\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanReadAlias:       \"production-traces-read\",\n\t\t\t\tServiceReadAlias:    \"production-services-read\",\n\t\t\t},\n\t\t\tindices: []string{\"production-traces-read\", \"production-services-read\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tSpanIndex:          spanIndexOpts,\n\t\t\t\tServiceIndex:       serviceIndexOpts,\n\t\t\t\tRemoteReadClusters: []string{\"cluster_one\", \"cluster_two\"},\n\t\t\t},\n\t\t\tindices: []string{\n\t\t\t\tspanIndexBaseName + spanDataLayoutFormat,\n\t\t\t\t\"cluster_one:\" + spanIndexBaseName + spanDataLayoutFormat,\n\t\t\t\t\"cluster_two:\" + spanIndexBaseName + spanDataLayoutFormat,\n\t\t\t\tserviceIndexBaseName + serviceDataLayoutFormat,\n\t\t\t\t\"cluster_one:\" + serviceIndexBaseName + serviceDataLayoutFormat,\n\t\t\t\t\"cluster_two:\" + serviceIndexBaseName + serviceDataLayoutFormat,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tUseReadWriteAliases: true, ReadAliasSuffix: \"archive\", RemoteReadClusters: []string{\"cluster_one\", \"cluster_two\"},\n\t\t\t},\n\t\t\tindices: []string{\n\t\t\t\tspanIndexBaseName + \"archive\",\n\t\t\t\t\"cluster_one:\" + spanIndexBaseName + \"archive\",\n\t\t\t\t\"cluster_two:\" + spanIndexBaseName + \"archive\",\n\t\t\t\tserviceIndexBaseName + \"archive\",\n\t\t\t\t\"cluster_one:\" + serviceIndexBaseName + \"archive\",\n\t\t\t\t\"cluster_two:\" + serviceIndexBaseName + \"archive\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tparams: SpanReaderParams{\n\t\t\t\tUseReadWriteAliases: true, RemoteReadClusters: []string{\"cluster_one\", \"cluster_two\"},\n\t\t\t},\n\t\t\tindices: []string{\n\t\t\t\tspanIndexBaseName + \"read\",\n\t\t\t\t\"cluster_one:\" + spanIndexBaseName + \"read\",\n\t\t\t\t\"cluster_two:\" + spanIndexBaseName + \"read\",\n\t\t\t\tserviceIndexBaseName + \"read\",\n\t\t\t\t\"cluster_one:\" + serviceIndexBaseName + \"read\",\n\t\t\t\t\"cluster_two:\" + serviceIndexBaseName + \"read\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\ttestCase.params.Client = clientFn\n\t\ttestCase.params.Logger = logger\n\t\ttestCase.params.Tracer = tracer.Tracer(\"test\")\n\t\tr := NewSpanReader(testCase.params)\n\n\t\tactualSpan := r.timeRangeIndices(r.spanIndexPrefix, r.spanIndex.DateLayout, date, date, -1*time.Hour)\n\t\tactualService := r.timeRangeIndices(r.serviceIndexPrefix, r.serviceIndex.DateLayout, date, date, -24*time.Hour)\n\t\tassert.Equal(t, testCase.indices, append(actualSpan, actualService...))\n\t}\n}\n\nfunc TestSpanReader_GetTrace(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\thits := make([]*elastic.SearchHit, 1)\n\t\thits[0] = &elastic.SearchHit{\n\t\t\tSource: exampleESSpan,\n\t\t}\n\t\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\t\tmockSearchService(r).Return(&elastic.SearchResult{Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t},\n\t\t\t}, nil)\n\t\tquery := []dbmodel.TraceID{dbmodel.TraceID(testingTraceId)}\n\t\ttrace, err := r.reader.GetTraces(context.Background(), query)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, trace)\n\t\tassert.Len(t, trace, 1)\n\t\texpectedSpans, err := r.reader.collectSpans(hits)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, trace[0].Spans, 1)\n\t\tassert.Equal(t, trace[0].Spans[0], expectedSpans[0])\n\t})\n}\n\nfunc newSearchRequest(fn *elastic.SearchSource) *elastic.SearchRequest {\n\treturn elastic.NewSearchRequest().\n\t\tIgnoreUnavailable(true).\n\t\tSource(fn)\n}\n\nfunc TestSpanReader_multiRead_followUp_query(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\ttraceID1 := dbmodel.TraceID(testingTraceId + \"1\")\n\t\ttraceID2 := dbmodel.TraceID(testingTraceId + \"2\")\n\t\tdate := time.Date(2019, 10, 10, 5, 0, 0, 0, time.UTC)\n\t\tspanID1 := dbmodel.Span{\n\t\t\tSpanID:    \"0\",\n\t\t\tTraceID:   traceID1,\n\t\t\tStartTime: model.TimeAsEpochMicroseconds(date),\n\t\t\tTags:      []dbmodel.KeyValue{},\n\t\t\tProcess: dbmodel.Process{\n\t\t\t\tTags: []dbmodel.KeyValue{},\n\t\t\t},\n\t\t}\n\t\tspanBytesID1, err := json.Marshal(spanID1)\n\t\trequire.NoError(t, err)\n\t\tspanID2 := dbmodel.Span{\n\t\t\tSpanID:    \"0\",\n\t\t\tTraceID:   traceID2,\n\t\t\tStartTime: model.TimeAsEpochMicroseconds(date),\n\t\t\tTags:      []dbmodel.KeyValue{},\n\t\t\tProcess: dbmodel.Process{\n\t\t\t\tTags: []dbmodel.KeyValue{},\n\t\t\t},\n\t\t}\n\t\tspanBytesID2, err := json.Marshal(spanID2)\n\t\trequire.NoError(t, err)\n\n\t\ttraceID1Query := elastic.NewTermQuery(traceIDField, string(traceID1))\n\t\tid1Query := elastic.NewBoolQuery().Must(traceID1Query)\n\t\tid1Search := newSearchRequest(r.reader.sourceFn(id1Query, model.TimeAsEpochMicroseconds(date.Add(-time.Hour))).TrackTotalHits(true))\n\t\ttraceID2Query := elastic.NewTermQuery(traceIDField, string(traceID2))\n\t\tid2Query := elastic.NewBoolQuery().Must(traceID2Query)\n\t\tid2Search := newSearchRequest(r.reader.sourceFn(id2Query, model.TimeAsEpochMicroseconds(date.Add(-time.Hour))).TrackTotalHits(true))\n\t\tid1SearchSpanTime := newSearchRequest(r.reader.sourceFn(id1Query, spanID1.StartTime).TrackTotalHits(true))\n\n\t\tmultiSearchService := &mocks.MultiSearchService{}\n\t\tfirstMultiSearch := &mocks.MultiSearchService{}\n\t\tsecondMultiSearch := &mocks.MultiSearchService{}\n\t\tmultiSearchService.On(\"Add\", mock.MatchedBy(func(searches []*elastic.SearchRequest) bool {\n\t\t\treturn len(searches) == 2 &&\n\t\t\t\treflect.DeepEqual(searches[0], id1Search) &&\n\t\t\t\treflect.DeepEqual(searches[1], id2Search)\n\t\t})).Return(firstMultiSearch).Once()\n\n\t\tmultiSearchService.On(\"Add\", mock.MatchedBy(func(searches []*elastic.SearchRequest) bool {\n\t\t\treturn len(searches) == 1 &&\n\t\t\t\treflect.DeepEqual(searches[0], id1SearchSpanTime)\n\t\t})).Return(secondMultiSearch).Once()\n\n\t\tfirstMultiSearch.On(\"Index\", mock.AnythingOfType(\"[]string\")).Return(firstMultiSearch)\n\t\tsecondMultiSearch.On(\"Index\", mock.AnythingOfType(\"[]string\")).Return(secondMultiSearch)\n\t\tr.client.On(\"MultiSearch\").Return(multiSearchService)\n\n\t\tfistMultiSearchMock := firstMultiSearch.On(\"Do\", mock.Anything)\n\t\tsecondMultiSearchMock := secondMultiSearch.On(\"Do\", mock.Anything)\n\n\t\t// set TotalHits to two to trigger the follow up query\n\t\t// the client will return only one span therefore the implementation\n\t\t// triggers follow up query for the same traceID with the timestamp of the last span\n\t\tsearchHitsID1 := &elastic.SearchHits{Hits: []*elastic.SearchHit{\n\t\t\t{Source: spanBytesID1},\n\t\t}, TotalHits: &elastic.TotalHits{\n\t\t\tValue:    2,\n\t\t\tRelation: \"eq\",\n\t\t}}\n\t\tfistMultiSearchMock.\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHitsID1},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\tsearchHitsID2 := &elastic.SearchHits{Hits: []*elastic.SearchHit{\n\t\t\t{Source: spanBytesID2},\n\t\t}, TotalHits: &elastic.TotalHits{\n\t\t\tValue:    1,\n\t\t\tRelation: \"eq\",\n\t\t}}\n\t\tsecondMultiSearchMock.\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHitsID2},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\ttraces, err := r.reader.multiRead(context.Background(), []dbmodel.TraceID{traceID1, traceID2}, date, date)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, traces)\n\t\trequire.Len(t, traces, 2)\n\n\t\tfor i, s := range []dbmodel.Span{spanID1, spanID2} {\n\t\t\tactual := traces[i].Spans[0]\n\t\t\tactualData, err := json.Marshal(actual)\n\t\t\trequire.NoError(t, err)\n\t\t\texpectedData, err := json.Marshal(s)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, string(expectedData), string(actualData))\n\t\t}\n\t})\n}\n\nfunc TestSpanReader_SearchAfter(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tvar hits []*elastic.SearchHit\n\n\t\tfor range 10000 {\n\t\t\thit := &elastic.SearchHit{Source: exampleESSpan}\n\t\t\thits = append(hits, hit)\n\t\t}\n\n\t\ttotalHits := &elastic.TotalHits{\n\t\t\tValue:    int64(10040),\n\t\t\tRelation: \"eq\",\n\t\t}\n\n\t\tsearchHits := &elastic.SearchHits{Hits: hits, TotalHits: totalHits}\n\n\t\tmockSearchService(r).Return(&elastic.SearchResult{Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t},\n\t\t\t}, nil).Times(2)\n\n\t\tquery := []dbmodel.TraceID{dbmodel.TraceID(\"testing-id\")}\n\t\ttrace, err := r.reader.GetTraces(context.Background(), query)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, trace)\n\t\tassert.Len(t, trace, 1)\n\t\texpectedSpans, err := r.reader.collectSpans(hits)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, trace[0].Spans[0], expectedSpans[0])\n\t})\n}\n\nfunc TestSpanReader_GetTraceQueryError(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(nil, errors.New(\"query error occurred\"))\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t}, nil)\n\t\tquery := []dbmodel.TraceID{dbmodel.TraceID(\"testing-id\")}\n\t\ttrace, err := r.reader.GetTraces(context.Background(), query)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.Empty(t, trace)\n\t})\n}\n\nfunc TestSpanReader_GetTraceNilHits(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tvar hits []*elastic.SearchHit\n\t\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\t\tmockSearchService(r).Return(&elastic.SearchResult{Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: nil},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\tquery := []dbmodel.TraceID{dbmodel.TraceID(testingTraceId)}\n\t\ttrace, err := r.reader.GetTraces(context.Background(), query)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.Empty(t, trace)\n\t})\n}\n\nfunc TestSpanReader_GetTraceInvalidSpanError(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tdata := []byte(`{\"TraceID\": \"123\"asdf fadsg}`)\n\t\thits := make([]*elastic.SearchHit, 1)\n\t\thits[0] = &elastic.SearchHit{\n\t\t\tSource: data,\n\t\t}\n\t\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\t\tmockSearchService(r).Return(&elastic.SearchResult{Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\tquery := []dbmodel.TraceID{dbmodel.TraceID(testingTraceId)}\n\t\ttrace, err := r.reader.GetTraces(context.Background(), query)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.Error(t, err, \"invalid span\")\n\t\trequire.Nil(t, trace)\n\t})\n}\n\nfunc TestSpanReader_esJSONtoJSONSpanModel(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tjsonPayload := exampleESSpan\n\n\t\tesSpanRaw := &elastic.SearchHit{\n\t\t\tSource: jsonPayload,\n\t\t}\n\n\t\tspan, err := r.reader.unmarshalJSONSpan(esSpanRaw)\n\t\trequire.NoError(t, err)\n\n\t\tvar expectedSpan dbmodel.Span\n\t\trequire.NoError(t, json.Unmarshal(exampleESSpan, &expectedSpan))\n\t\tassert.Equal(t, expectedSpan, span)\n\t})\n}\n\nfunc TestSpanReader_esJSONtoJSONSpanModelError(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tdata := []byte(`{\"TraceID\": \"123\"asdf fadsg}`)\n\t\tjsonPayload := data\n\n\t\tesSpanRaw := &elastic.SearchHit{\n\t\t\tSource: jsonPayload,\n\t\t}\n\n\t\t_, err := r.reader.unmarshalJSONSpan(esSpanRaw)\n\t\trequire.Error(t, err)\n\t})\n}\n\nfunc TestSpanReaderFindIndices(t *testing.T) {\n\ttoday := time.Date(1995, time.April, 21, 4, 12, 19, 95, time.UTC)\n\tyesterday := today.AddDate(0, 0, -1)\n\ttwoDaysAgo := today.AddDate(0, 0, -2)\n\tdateLayout := \"2006-01-02\"\n\n\ttestCases := []struct {\n\t\tstartTime time.Time\n\t\tendTime   time.Time\n\t\texpected  []string\n\t}{\n\t\t{\n\t\t\tstartTime: today.Add(-time.Millisecond),\n\t\t\tendTime:   today,\n\t\t\texpected: []string{\n\t\t\t\tindexWithDate(spanIndexBaseName, dateLayout, today),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tstartTime: today.Add(-13 * time.Hour),\n\t\t\tendTime:   today,\n\t\t\texpected: []string{\n\t\t\t\tindexWithDate(spanIndexBaseName, dateLayout, today),\n\t\t\t\tindexWithDate(spanIndexBaseName, dateLayout, yesterday),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tstartTime: today.Add(-48 * time.Hour),\n\t\t\tendTime:   today,\n\t\t\texpected: []string{\n\t\t\t\tindexWithDate(spanIndexBaseName, dateLayout, today),\n\t\t\t\tindexWithDate(spanIndexBaseName, dateLayout, yesterday),\n\t\t\t\tindexWithDate(spanIndexBaseName, dateLayout, twoDaysAgo),\n\t\t\t},\n\t\t},\n\t}\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tfor _, testCase := range testCases {\n\t\t\tactual := r.reader.timeRangeIndices(spanIndexBaseName, dateLayout, testCase.startTime, testCase.endTime, -24*time.Hour)\n\t\t\tassert.Equal(t, testCase.expected, actual)\n\t\t}\n\t})\n}\n\nfunc TestSpanReader_indexWithDate(t *testing.T) {\n\twithSpanReader(t, func(_ *spanReaderTest) {\n\t\tactual := indexWithDate(spanIndexBaseName, \"2006-01-02\", time.Date(1995, time.April, 21, 4, 21, 19, 95, time.UTC))\n\t\tassert.Equal(t, \"jaeger-span-1995-04-21\", actual)\n\t})\n}\n\nfunc testGet(typ string, t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\trawMessage := []byte(`{\"buckets\": [{\"key\": \"123\",\"doc_count\": 16}]}`)\n\tgoodAggregations[typ] = rawMessage\n\n\tbadAggregations := make(map[string]json.RawMessage)\n\tbadRawMessage := []byte(`{\"buckets\": [{bad json]}asdf`)\n\tbadAggregations[typ] = badRawMessage\n\n\ttestCases := []struct {\n\t\tcaption        string\n\t\tsearchResult   *elastic.SearchResult\n\t\tsearchError    error\n\t\texpectedError  func() string\n\t\texpectedOutput map[string]any\n\t}{\n\t\t{\n\t\t\tcaption:      typ + \" full behavior\",\n\t\t\tsearchResult: &elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations)},\n\t\t\texpectedOutput: map[string]any{\n\t\t\t\toperationsAggregation: []dbmodel.Operation{{Name: \"123\"}},\n\t\t\t\ttraceIDAggregation:    []dbmodel.TraceID{\"123\"},\n\t\t\t\t\"default\":             []string{\"123\"},\n\t\t\t},\n\t\t\texpectedError: func() string {\n\t\t\t\treturn \"\"\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:     typ + \" search error\",\n\t\t\tsearchError: errors.New(\"Search failure\"),\n\t\t\texpectedError: func() string {\n\t\t\t\tif typ == operationsAggregation {\n\t\t\t\t\treturn \"search operations failed: Search failure\"\n\t\t\t\t}\n\t\t\t\treturn \"search services failed: Search failure\"\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcaption:      typ + \" search error\",\n\t\t\tsearchResult: &elastic.SearchResult{Aggregations: elastic.Aggregations(badAggregations)},\n\t\t\texpectedError: func() string {\n\t\t\t\treturn \"could not find aggregation of \" + typ\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttestCase := tc\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithSpanReader(t, func(r *spanReaderTest) {\n\t\t\t\tmockSearchService(r).Return(testCase.searchResult, testCase.searchError)\n\t\t\t\tactual, err := returnSearchFunc(typ, r)\n\t\t\t\tif testCase.expectedError() != \"\" {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError())\n\t\t\t\t\tassert.Nil(t, actual)\n\t\t\t\t} else if expectedOutput, ok := testCase.expectedOutput[typ]; ok {\n\t\t\t\t\tassert.Equal(t, expectedOutput, actual)\n\t\t\t\t} else {\n\t\t\t\t\tassert.Equal(t, testCase.expectedOutput[\"default\"], actual)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc returnSearchFunc(typ string, r *spanReaderTest) (any, error) {\n\tswitch typ {\n\tcase servicesAggregation:\n\t\treturn r.reader.GetServices(context.Background())\n\tcase operationsAggregation:\n\t\treturn r.reader.GetOperations(\n\t\t\tcontext.Background(),\n\t\t\tdbmodel.OperationQueryParameters{ServiceName: \"someService\"},\n\t\t)\n\tcase traceIDAggregation:\n\t\treturn r.reader.findTraceIDsFromQuery(context.Background(), dbmodel.TraceQueryParameters{})\n\tdefault:\n\t\treturn nil, errors.New(\"Specify services, operations, traceIDs only\")\n\t}\n}\n\nfunc TestSpanReader_bucketToStringArray(t *testing.T) {\n\twithSpanReader(t, func(_ *spanReaderTest) {\n\t\tbuckets := make([]*elastic.AggregationBucketKeyItem, 3)\n\t\tbuckets[0] = &elastic.AggregationBucketKeyItem{Key: \"hello\"}\n\t\tbuckets[1] = &elastic.AggregationBucketKeyItem{Key: \"world\"}\n\t\tbuckets[2] = &elastic.AggregationBucketKeyItem{Key: \"2\"}\n\n\t\tactual, err := bucketToStringArray[string](buckets)\n\t\trequire.NoError(t, err)\n\n\t\tassert.Equal(t, []string{\"hello\", \"world\", \"2\"}, actual)\n\t})\n}\n\nfunc TestSpanReader_bucketToStringArrayError(t *testing.T) {\n\twithSpanReader(t, func(_ *spanReaderTest) {\n\t\tbuckets := make([]*elastic.AggregationBucketKeyItem, 3)\n\t\tbuckets[0] = &elastic.AggregationBucketKeyItem{Key: \"hello\"}\n\t\tbuckets[1] = &elastic.AggregationBucketKeyItem{Key: \"world\"}\n\t\tbuckets[2] = &elastic.AggregationBucketKeyItem{Key: 2}\n\n\t\t_, err := bucketToStringArray[string](buckets)\n\t\trequire.EqualError(t, err, \"non-string key found in aggregation\")\n\t})\n}\n\nfunc TestSpanReader_FindTraces(t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\trawMessage := []byte(`{\"buckets\": [{\"key\": \"1\",\"doc_count\": 16},{\"key\": \"2\",\"doc_count\": 16},{\"key\": \"3\",\"doc_count\": 16}]}`)\n\tgoodAggregations[traceIDAggregation] = rawMessage\n\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: exampleESSpan,\n\t}\n\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\t// find trace IDs\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations), Hits: searchHits}, nil)\n\t\t// bulk read traces\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: serviceName,\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t\tNumTraces:    1,\n\t\t}\n\n\t\ttraces, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 1)\n\n\t\ttrace := traces[0]\n\t\texpectedSpans, err := r.reader.collectSpans(hits)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Len(t, trace.Spans, 2)\n\t\tassert.Equal(t, trace.Spans[0], expectedSpans[0])\n\t})\n}\n\nfunc TestSpanReader_FindTracesInvalidQuery(t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\trawMessage := []byte(`{\"buckets\": [{\"key\": \"1\",\"doc_count\": 16},{\"key\": \"2\",\"doc_count\": 16},{\"key\": \"3\",\"doc_count\": 16}]}`)\n\tgoodAggregations[traceIDAggregation] = rawMessage\n\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: exampleESSpan,\n\t}\n\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations), Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: \"\",\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t}\n\n\t\ttraces, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.Error(t, err)\n\t\tassert.Nil(t, traces)\n\t})\n}\n\nfunc TestSpanReader_FindTracesAggregationFailure(t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: exampleESSpan,\n\t}\n\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations), Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t}, nil)\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: serviceName,\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t}\n\n\t\ttraces, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.Error(t, err)\n\t\tassert.Nil(t, traces)\n\t})\n}\n\nfunc TestSpanReader_FindTracesNoTraceIDs(t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\trawMessage := []byte(`{\"buckets\": []}`)\n\tgoodAggregations[traceIDAggregation] = rawMessage\n\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: exampleESSpan,\n\t}\n\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations), Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t}, nil)\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: serviceName,\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t}\n\n\t\ttraces, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, traces)\n\t})\n}\n\nfunc TestSpanReader_FindTracesReadTraceFailure(t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\trawMessage := []byte(`{\"buckets\": [{\"key\": \"1\",\"doc_count\": 16},{\"key\": \"2\",\"doc_count\": 16}]}`)\n\tgoodAggregations[traceIDAggregation] = rawMessage\n\n\tbadSpan := []byte(`{\"TraceID\": \"123\"asjlgajdfhilqghi[adfvca} bad json`)\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: badSpan,\n\t}\n\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations), Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(nil, errors.New(\"read error\"))\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: serviceName,\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t}\n\n\t\ttraces, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.EqualError(t, err, \"read error\")\n\t\tassert.Empty(t, traces)\n\t})\n}\n\nfunc TestSpanReader_FindTracesSpanCollectionFailure(t *testing.T) {\n\tgoodAggregations := make(map[string]json.RawMessage)\n\trawMessage := []byte(`{\"buckets\": [{\"key\": \"1\",\"doc_count\": 16},{\"key\": \"2\",\"doc_count\": 16}]}`)\n\tgoodAggregations[traceIDAggregation] = rawMessage\n\n\tbadSpan := []byte(`{\"TraceID\": \"123\"asjlgajdfhilqghi[adfvca} bad json`)\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: badSpan,\n\t}\n\tsearchHits := &elastic.SearchHits{Hits: hits}\n\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{Aggregations: elastic.Aggregations(goodAggregations), Hits: searchHits}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t\t{Hits: searchHits},\n\t\t\t\t},\n\t\t\t}, nil)\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: serviceName,\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t}\n\n\t\ttraces, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.Error(t, err)\n\t\tassert.Empty(t, traces)\n\t})\n}\n\nfunc TestFindTraceIDs(t *testing.T) {\n\ttestCases := []struct {\n\t\taggregrationID string\n\t}{\n\t\t{traceIDAggregation},\n\t\t{servicesAggregation},\n\t\t{operationsAggregation},\n\t}\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.aggregrationID, func(t *testing.T) {\n\t\t\ttestGet(testCase.aggregrationID, t)\n\t\t})\n\t}\n}\n\nfunc TestReturnSearchFunc_DefaultCase(t *testing.T) {\n\tr := &spanReaderTest{}\n\n\tresult, err := returnSearchFunc(\"unknownAggregationType\", r)\n\n\tassert.Nil(t, result)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"Specify services, operations, traceIDs only\")\n}\n\nfunc mockMultiSearchService(r *spanReaderTest) *mock.Call {\n\tmultiSearchService := &mocks.MultiSearchService{}\n\tmultiSearchService.On(\"Add\", mock.Anything, mock.Anything, mock.Anything).Return(multiSearchService)\n\tmultiSearchService.On(\"Index\", mock.AnythingOfType(\"[]string\")).Return(multiSearchService)\n\tr.client.On(\"MultiSearch\").Return(multiSearchService)\n\treturn multiSearchService.On(\"Do\", mock.Anything)\n}\n\nfunc mockArchiveMultiSearchService(r *spanReaderTest, indexName []string) *mock.Call {\n\tmultiSearchService := &mocks.MultiSearchService{}\n\tmultiSearchService.On(\"Add\", mock.Anything, mock.Anything, mock.Anything).Return(multiSearchService)\n\tmultiSearchService.On(\"Index\", indexName).Return(multiSearchService)\n\tr.client.On(\"MultiSearch\").Return(multiSearchService)\n\treturn multiSearchService.On(\"Do\", mock.Anything)\n}\n\n// matchTermsAggregation uses reflection to match the size attribute of the TermsAggregation; neither\n// attributes nor getters are exported by TermsAggregation.\nfunc matchTermsAggregation(termsAgg *elastic.TermsAggregation) bool {\n\tval := reflect.ValueOf(termsAgg).Elem()\n\tsizeVal := val.FieldByName(\"size\").Elem().Int()\n\treturn sizeVal == defaultMaxDocCount\n}\n\nfunc mockSearchService(r *spanReaderTest) *mock.Call {\n\tsearchService := &mocks.SearchService{}\n\tsearchService.On(\"Query\", mock.Anything).Return(searchService)\n\tsearchService.On(\"IgnoreUnavailable\", mock.AnythingOfType(\"bool\")).Return(searchService)\n\tsearchService.On(\"Size\", mock.MatchedBy(func(size int) bool {\n\t\treturn size == 0 // Aggregations apply size (bucket) limits in their own query objects, and do not apply at the parent query level.\n\t})).Return(searchService)\n\tsearchService.On(\"Aggregation\", stringMatcher(servicesAggregation), mock.MatchedBy(matchTermsAggregation)).Return(searchService)\n\tsearchService.On(\"Aggregation\", stringMatcher(operationsAggregation), mock.MatchedBy(matchTermsAggregation)).Return(searchService)\n\tsearchService.On(\"Aggregation\", stringMatcher(traceIDAggregation), mock.AnythingOfType(\"*elastic.TermsAggregation\")).Return(searchService)\n\tr.client.On(\"Search\", mock.AnythingOfType(\"[]string\")).Return(searchService)\n\treturn searchService.On(\"Do\", mock.Anything)\n}\n\nfunc TestTraceQueryParameterValidation(t *testing.T) {\n\ttqp := dbmodel.TraceQueryParameters{\n\t\tServiceName: \"\",\n\t\tTags: map[string]string{\n\t\t\t\"hello\": \"world\",\n\t\t},\n\t}\n\terr := validateQuery(tqp)\n\trequire.EqualError(t, err, ErrServiceNameNotSet.Error())\n\n\ttqp.ServiceName = serviceName\n\n\ttqp.StartTimeMin = time.Time{} // time.Unix(0,0) doesn't work because timezones\n\ttqp.StartTimeMax = time.Time{}\n\terr = validateQuery(tqp)\n\trequire.EqualError(t, err, ErrStartAndEndTimeNotSet.Error())\n\n\ttqp.StartTimeMin = time.Now()\n\ttqp.StartTimeMax = time.Now().Add(-1 * time.Hour)\n\terr = validateQuery(tqp)\n\trequire.EqualError(t, err, ErrStartTimeMinGreaterThanMax.Error())\n\n\ttqp.StartTimeMin = time.Now().Add(-1 * time.Hour)\n\ttqp.StartTimeMax = time.Now()\n\terr = validateQuery(tqp)\n\trequire.NoError(t, err)\n\n\ttqp.DurationMin = time.Hour\n\ttqp.DurationMax = time.Minute\n\terr = validateQuery(tqp)\n\trequire.EqualError(t, err, ErrDurationMinGreaterThanMax.Error())\n}\n\nfunc TestSpanReader_buildTraceIDAggregation(t *testing.T) {\n\texpectedStr := `{ \"terms\":{\n            \"field\":\"traceID\",\n            \"size\":123,\n            \"order\":{\n               \"startTime\":\"desc\"\n            }\n         },\n         \"aggregations\": {\n            \"startTime\" : { \"max\": {\"field\": \"startTime\"}}\n         }}`\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\ttraceIDAggregation := r.reader.buildTraceIDAggregation(123)\n\t\tactual, err := traceIDAggregation.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal([]byte(expectedStr), &expected)\n\t\texpected[\"terms\"].(map[string]any)[\"size\"] = 123\n\t\texpected[\"terms\"].(map[string]any)[\"order\"] = []any{map[string]string{\"startTime\": \"desc\"}}\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildFindTraceIDsQuery(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tDurationMin:   time.Second,\n\t\t\tDurationMax:   time.Second * 2,\n\t\t\tStartTimeMin:  time.Time{},\n\t\t\tStartTimeMax:  time.Time{}.Add(time.Second),\n\t\t\tServiceName:   \"s\",\n\t\t\tOperationName: \"o\",\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t}\n\n\t\tactualQuery := r.reader.buildFindTraceIDsQuery(traceQuery)\n\t\tactual, err := actualQuery.Source()\n\t\trequire.NoError(t, err)\n\t\texpectedQuery := elastic.NewBoolQuery().\n\t\t\tMust(\n\t\t\t\tr.reader.buildDurationQuery(time.Second, time.Second*2),\n\t\t\t\tr.reader.buildStartTimeQuery(time.Time{}, time.Time{}.Add(time.Second)),\n\t\t\t\tr.reader.buildServiceNameQuery(\"s\"),\n\t\t\t\tr.reader.buildOperationNameQuery(\"o\"),\n\t\t\t\tr.reader.buildTagQuery(\"hello\", \"world\"),\n\t\t\t)\n\t\texpected, err := expectedQuery.Source()\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildDurationQuery(t *testing.T) {\n\texpectedStr := `{ \"range\":\n\t\t\t{ \"duration\": {\n\t\t\t\t        \"gte\": 1000000,\n\t\t\t\t        \"lte\": 2000000 }\n\t\t\t}\n\t\t}`\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tdurationMin := time.Second\n\t\tdurationMax := time.Second * 2\n\t\tdurationQuery := r.reader.buildDurationQuery(durationMin, durationMax)\n\t\tactual, err := durationQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal([]byte(expectedStr), &expected)\n\t\t// We need to do this because we cannot process a json into uint64.\n\t\texpected[\"range\"].(map[string]any)[\"duration\"].(map[string]any)[\"gte\"] = model.DurationAsMicroseconds(durationMin)\n\t\texpected[\"range\"].(map[string]any)[\"duration\"].(map[string]any)[\"lte\"] = model.DurationAsMicroseconds(durationMax)\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildStartTimeQuery(t *testing.T) {\n\texpectedStr := `{ \"range\":\n\t\t\t{ \"startTimeMillis\": {\n\t\t\t\t         \"gte\": 1000000,\n\t\t\t\t         \"lte\": 2000000 }\n\t\t\t}\n\t\t}`\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tstartTimeMin := time.Time{}.Add(time.Second)\n\t\tstartTimeMax := time.Time{}.Add(2 * time.Second)\n\t\tdurationQuery := r.reader.buildStartTimeQuery(startTimeMin, startTimeMax)\n\t\tactual, err := durationQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal([]byte(expectedStr), &expected)\n\t\t// We need to do this because we cannot process a json into uint64.\n\t\texpected[\"range\"].(map[string]any)[\"startTimeMillis\"].(map[string]any)[\"gte\"] = model.TimeAsEpochMicroseconds(startTimeMin) / 1000\n\t\texpected[\"range\"].(map[string]any)[\"startTimeMillis\"].(map[string]any)[\"lte\"] = model.TimeAsEpochMicroseconds(startTimeMax) / 1000\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildServiceNameQuery(t *testing.T) {\n\texpectedStr := `{ \"match\": { \"process.serviceName\": { \"query\": \"bat\" }}}`\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tserviceNameQuery := r.reader.buildServiceNameQuery(\"bat\")\n\t\tactual, err := serviceNameQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal([]byte(expectedStr), &expected)\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildOperationNameQuery(t *testing.T) {\n\texpectedStr := `{ \"match\": { \"operationName\": { \"query\": \"spook\" }}}`\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\toperationNameQuery := r.reader.buildOperationNameQuery(\"spook\")\n\t\tactual, err := operationNameQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal([]byte(expectedStr), &expected)\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildTagQuery(t *testing.T) {\n\tinStr, err := os.ReadFile(\"fixtures/query_01.json\")\n\trequire.NoError(t, err)\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\ttagQuery := r.reader.buildTagQuery(\"bat.foo\", \"spook\")\n\t\tactual, err := tagQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal(inStr, &expected)\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildTagRegexQuery(t *testing.T) {\n\tinStr, err := os.ReadFile(\"fixtures/query_02.json\")\n\trequire.NoError(t, err)\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\ttagQuery := r.reader.buildTagQuery(\"bat.foo\", \"spo.*\")\n\t\tactual, err := tagQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal(inStr, &expected)\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_buildTagRegexEscapedQuery(t *testing.T) {\n\tinStr, err := os.ReadFile(\"fixtures/query_03.json\")\n\trequire.NoError(t, err)\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\ttagQuery := r.reader.buildTagQuery(\"bat.foo\", \"spo\\\\*\")\n\t\tactual, err := tagQuery.Source()\n\t\trequire.NoError(t, err)\n\n\t\texpected := make(map[string]any)\n\t\tjson.Unmarshal(inStr, &expected)\n\n\t\tassert.EqualValues(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReader_GetEmptyIndex(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t}, nil)\n\n\t\ttraceQuery := dbmodel.TraceQueryParameters{\n\t\t\tServiceName: serviceName,\n\t\t\tTags: map[string]string{\n\t\t\t\t\"hello\": \"world\",\n\t\t\t},\n\t\t\tStartTimeMin: time.Now().Add(-1 * time.Hour),\n\t\t\tStartTimeMax: time.Now(),\n\t\t\tNumTraces:    2,\n\t\t}\n\n\t\tservices, err := r.reader.FindTraces(context.Background(), traceQuery)\n\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, services)\n\t})\n}\n\nfunc TestSpanReader_ArchiveTraces(t *testing.T) {\n\ttestCases := []struct {\n\t\tuseAliases bool\n\t\tsuffix     string\n\t\texpected   string\n\t}{\n\t\t{false, \"\", \"jaeger-span-\"},\n\t\t{true, \"\", \"jaeger-span-read\"},\n\t\t{false, \"foobar\", \"jaeger-span-\"},\n\t\t{true, \"foobar\", \"jaeger-span-foobar\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(fmt.Sprintf(\"useAliases=%v suffix=%s\", tc.useAliases, tc.suffix), func(t *testing.T) {\n\t\t\twithArchiveSpanReader(t, tc.useAliases, tc.suffix, func(r *spanReaderTest) {\n\t\t\t\tmockSearchService(r).\n\t\t\t\t\tReturn(&elastic.SearchResult{}, nil)\n\t\t\t\tmockArchiveMultiSearchService(r, []string{tc.expected}).\n\t\t\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t\t\t}, nil)\n\t\t\t\tquery := []dbmodel.TraceID{}\n\t\t\t\ttrace, err := r.reader.GetTraces(context.Background(), query)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotEmpty(t, r.traceBuffer.GetSpans(), \"Spans recorded\")\n\t\t\t\trequire.Empty(t, trace)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestBuildTraceByIDQuery(t *testing.T) {\n\ttests := []struct {\n\t\ttraceID string\n\t\tquery   elastic.Query\n\t}{\n\t\t{\n\t\t\ttraceID: \"0000000000000001\",\n\t\t\tquery:   elastic.NewTermQuery(traceIDField, \"0000000000000001\"),\n\t\t},\n\t\t{\n\t\t\ttraceID: \"00000000000000010000000000000001\",\n\t\t\tquery:   elastic.NewTermQuery(traceIDField, \"00000000000000010000000000000001\"),\n\t\t},\n\t\t{\n\t\t\ttraceID: \"ffffffffffffffffffffffffffffffff\",\n\t\t\tquery:   elastic.NewTermQuery(traceIDField, \"ffffffffffffffffffffffffffffffff\"),\n\t\t},\n\t\t{\n\t\t\ttraceID: \"0short-traceid\",\n\t\t\tquery:   elastic.NewTermQuery(traceIDField, \"0short-traceid\"),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.traceID, func(t *testing.T) {\n\t\t\tq := buildTraceByIDQuery(dbmodel.TraceID(test.traceID))\n\t\t\tassert.Equal(t, test.query, q)\n\t\t})\n\t}\n}\n\nfunc TestTerminateAfterNotSet(t *testing.T) {\n\tsrcFn := getSourceFn(99)\n\tsearchSource := srcFn(elastic.NewMatchAllQuery(), 1)\n\tsp, err := searchSource.Source()\n\trequire.NoError(t, err)\n\n\tsearchParams, ok := sp.(map[string]any)\n\trequire.True(t, ok)\n\n\ttermAfter, ok := searchParams[\"terminate_after\"]\n\trequire.False(t, ok)\n\tassert.Nil(t, termAfter)\n\n\tquery, ok := searchParams[\"query\"]\n\trequire.True(t, ok)\n\n\tqueryMap, ok := query.(map[string]any)\n\trequire.True(t, ok)\n\t_, ok = queryMap[\"match_all\"]\n\trequire.True(t, ok)\n\n\tsize, ok := searchParams[\"size\"]\n\trequire.True(t, ok)\n\tassert.Equal(t, 99, size)\n}\n\nfunc TestTagsMap(t *testing.T) {\n\ttests := []struct {\n\t\tfieldTags map[string]any\n\t\texpected  dbmodel.KeyValue\n\t}{\n\t\t{fieldTags: map[string]any{\"bool:bool\": true}, expected: dbmodel.KeyValue{Key: \"bool.bool\", Value: true, Type: dbmodel.BoolType}},\n\t\t{fieldTags: map[string]any{\"int.int\": int64(1)}, expected: dbmodel.KeyValue{Key: \"int.int\", Value: int64(1), Type: dbmodel.Int64Type}},\n\t\t{fieldTags: map[string]any{\"int:int\": int64(2)}, expected: dbmodel.KeyValue{Key: \"int.int\", Value: int64(2), Type: dbmodel.Int64Type}},\n\t\t{fieldTags: map[string]any{\"float\": float64(1.1)}, expected: dbmodel.KeyValue{Key: \"float\", Value: float64(1.1), Type: dbmodel.Float64Type}},\n\t\t{fieldTags: map[string]any{\"float\": float64(123)}, expected: dbmodel.KeyValue{Key: \"float\", Value: float64(123), Type: dbmodel.Float64Type}},\n\t\t{fieldTags: map[string]any{\"float\": float64(123.0)}, expected: dbmodel.KeyValue{Key: \"float\", Value: float64(123.0), Type: dbmodel.Float64Type}},\n\t\t{fieldTags: map[string]any{\"float:float\": float64(123)}, expected: dbmodel.KeyValue{Key: \"float.float\", Value: float64(123), Type: dbmodel.Float64Type}},\n\t\t{fieldTags: map[string]any{\"json_number:int\": json.Number(\"123\")}, expected: dbmodel.KeyValue{Key: \"json_number.int\", Value: int64(123), Type: dbmodel.Int64Type}},\n\t\t{fieldTags: map[string]any{\"json_number:float\": json.Number(\"123.0\")}, expected: dbmodel.KeyValue{Key: \"json_number.float\", Value: float64(123.0), Type: dbmodel.Float64Type}},\n\t\t{fieldTags: map[string]any{\"json_number:err\": json.Number(\"foo\")}, expected: dbmodel.KeyValue{Key: \"json_number.err\", Value: \"invalid tag type in foo: strconv.ParseFloat: parsing \\\"foo\\\": invalid syntax\", Type: dbmodel.StringType}},\n\t\t{fieldTags: map[string]any{\"str\": \"foo\"}, expected: dbmodel.KeyValue{Key: \"str\", Value: \"foo\", Type: dbmodel.StringType}},\n\t\t{fieldTags: map[string]any{\"str:str\": \"foo\"}, expected: dbmodel.KeyValue{Key: \"str.str\", Value: \"foo\", Type: dbmodel.StringType}},\n\t\t{fieldTags: map[string]any{\"binary\": []byte(\"foo\")}, expected: dbmodel.KeyValue{Key: \"binary\", Value: []byte(\"foo\"), Type: dbmodel.BinaryType}},\n\t\t{fieldTags: map[string]any{\"binary:binary\": []byte(\"foo\")}, expected: dbmodel.KeyValue{Key: \"binary.binary\", Value: []byte(\"foo\"), Type: dbmodel.BinaryType}},\n\t\t{fieldTags: map[string]any{\"unsupported\": struct{}{}}, expected: dbmodel.KeyValue{Key: \"unsupported\", Value: fmt.Sprintf(\"invalid tag type in %+v\", struct{}{}), Type: dbmodel.StringType}},\n\t}\n\treader := NewSpanReader(SpanReaderParams{\n\t\tTagDotReplacement: \":\",\n\t\tLogger:            zap.NewNop(),\n\t})\n\tfor i, test := range tests {\n\t\tt.Run(fmt.Sprintf(\"%d, %s\", i, test.fieldTags), func(t *testing.T) {\n\t\t\ttags := []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"testing-key\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: \"testing-value\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tspanTags := make(map[string]any)\n\t\t\tmaps.Copy(spanTags, test.fieldTags)\n\t\t\tspan := &dbmodel.Span{\n\t\t\t\tProcess: dbmodel.Process{\n\t\t\t\t\tTag:  test.fieldTags,\n\t\t\t\t\tTags: tags,\n\t\t\t\t},\n\t\t\t\tTag:  spanTags,\n\t\t\t\tTags: tags,\n\t\t\t}\n\t\t\treader.mergeAllNestedAndElevatedTagsOfSpan(span)\n\t\t\ttags = append(tags, test.expected)\n\t\t\tassert.Empty(t, span.Tag)\n\t\t\tassert.Empty(t, span.Process.Tag)\n\t\t\tassert.Equal(t, tags, span.Tags)\n\t\t\tassert.Equal(t, tags, span.Process.Tags)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/readerv1.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\nvar _ spanstore.Reader = (*SpanReaderV1)(nil) // check API conformance\n\n// SpanReaderV1\tis a wrapper around SpanReader\ntype SpanReaderV1 struct {\n\tspanReader    CoreSpanReader\n\tspanConverter ToDomain\n}\n\n// NewSpanReaderV1 returns an instance of SpanReaderV1\nfunc NewSpanReaderV1(p SpanReaderParams) *SpanReaderV1 {\n\treturn &SpanReaderV1{\n\t\tspanReader:    NewSpanReader(p),\n\t\tspanConverter: NewToDomain(),\n\t}\n}\n\n// GetTrace takes a traceID and returns a Trace associated with that traceID\nfunc (s *SpanReaderV1) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\ttraces, err := s.spanReader.GetTraces(ctx, []dbmodel.TraceID{dbmodel.TraceID(query.TraceID.String())})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(traces) == 0 {\n\t\treturn nil, spanstore.ErrTraceNotFound\n\t}\n\tspans, err := s.collectSpans(traces[0].Spans)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &model.Trace{Spans: spans}, nil\n}\n\nfunc (s *SpanReaderV1) collectSpans(dbSpans []dbmodel.Span) ([]*model.Span, error) {\n\tspans := make([]*model.Span, len(dbSpans))\n\tfor i := range dbSpans {\n\t\tspan, err := s.spanConverter.SpanToDomain(&dbSpans[i])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"converting ES dbSpan to domain Span failed: %w\", err)\n\t\t}\n\t\tspans[i] = span\n\t}\n\treturn spans, nil\n}\n\n// GetOperations returns all operations for a specific service traced by Jaeger\nfunc (s *SpanReaderV1) GetOperations(\n\tctx context.Context,\n\tquery spanstore.OperationQueryParameters,\n) ([]spanstore.Operation, error) {\n\tdbmodelQuery := dbmodel.OperationQueryParameters{\n\t\tServiceName: query.ServiceName,\n\t\tSpanKind:    query.SpanKind,\n\t}\n\toperations, err := s.spanReader.GetOperations(ctx, dbmodelQuery)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar result []spanstore.Operation\n\n\tfor _, operation := range operations {\n\t\tresult = append(result, spanstore.Operation{\n\t\t\tName:     operation.Name,\n\t\t\tSpanKind: operation.SpanKind,\n\t\t})\n\t}\n\treturn result, nil\n}\n\n// GetServices returns all services traced by Jaeger, ordered by frequency\nfunc (s *SpanReaderV1) GetServices(ctx context.Context) ([]string, error) {\n\treturn s.spanReader.GetServices(ctx)\n}\n\n// FindTraces retrieves traces that match the traceQuery\nfunc (s *SpanReaderV1) FindTraces(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]*model.Trace, error) {\n\ttraces, err := s.spanReader.FindTraces(ctx, toDbQueryParams(traceQuery))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar result []*model.Trace\n\tfor _, trace := range traces {\n\t\tspans, err := s.collectSpans(trace.Spans)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult = append(result, &model.Trace{Spans: spans})\n\t}\n\treturn result, nil\n}\n\n// FindTraceIDs retrieves traces IDs that match the traceQuery\nfunc (s *SpanReaderV1) FindTraceIDs(ctx context.Context, traceQuery *spanstore.TraceQueryParameters) ([]model.TraceID, error) {\n\tids, err := s.spanReader.FindTraceIDs(ctx, toDbQueryParams(traceQuery))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn toModelTraceIDs(ids)\n}\n\nfunc toDbQueryParams(p *spanstore.TraceQueryParameters) dbmodel.TraceQueryParameters {\n\treturn dbmodel.TraceQueryParameters{\n\t\tServiceName:   p.ServiceName,\n\t\tOperationName: p.OperationName,\n\t\tTags:          p.Tags,\n\t\tStartTimeMin:  p.StartTimeMin,\n\t\tStartTimeMax:  p.StartTimeMax,\n\t\tDurationMin:   p.DurationMin,\n\t\tDurationMax:   p.DurationMax,\n\t\tNumTraces:     p.NumTraces,\n\t}\n}\n\nfunc toModelTraceIDs(traceIDs []dbmodel.TraceID) ([]model.TraceID, error) {\n\ttraceIDsMap := map[model.TraceID]bool{}\n\t// https://github.com/jaegertracing/jaeger/pull/1956 added leading zeros to IDs\n\t// So we need to also read IDs without leading zeros for compatibility with previously saved data.\n\t// That means the input to this function may contain logically identical trace IDs but formatted\n\t// with or without padding, and we need to dedupe them.\n\t// TODO remove deduping in newer versions, added in Jaeger 1.16\n\ttraceIDsModels := make([]model.TraceID, 0, len(traceIDs))\n\tfor _, ID := range traceIDs {\n\t\ttraceID, err := model.TraceIDFromString(string(ID))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"making traceID from string '%s' failed: %w\", ID, err)\n\t\t}\n\t\tif _, ok := traceIDsMap[traceID]; !ok {\n\t\t\ttraceIDsMap[traceID] = true\n\t\t\ttraceIDsModels = append(traceIDsModels, traceID)\n\t\t}\n\t}\n\n\treturn traceIDsModels, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/readerv1_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore/mocks\"\n)\n\nfunc withSpanReaderV1(fn func(r *SpanReaderV1, m *mocks.CoreSpanReader)) {\n\tspanReader := &mocks.CoreSpanReader{}\n\tr := &SpanReaderV1{\n\t\tspanReader: spanReader,\n\t}\n\tfn(r, spanReader)\n}\n\nfunc getTestingTrace(traceID model.TraceID, spanId model.SpanID) dbmodel.Trace {\n\treturn dbmodel.Trace{Spans: []dbmodel.Span{{\n\t\tTraceID: dbmodel.TraceID(traceID.String()),\n\t\tSpanID:  dbmodel.SpanID(spanId.String()),\n\t}}}\n}\n\nfunc TestSpanReaderV1_GetTrace(t *testing.T) {\n\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\ttraceID1 := model.NewTraceID(0, 1)\n\t\tspanID1 := model.NewSpanID(1)\n\t\ttrace := getTestingTrace(traceID1, spanID1)\n\t\tm.On(\"GetTraces\", mock.Anything, mock.Anything).Return([]dbmodel.Trace{trace}, nil)\n\t\tactual, err := r.GetTrace(context.Background(), spanstore.GetTraceParameters{})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, actual.Spans, 1)\n\t\tassert.Equal(t, traceID1, actual.Spans[0].TraceID)\n\t})\n}\n\nfunc TestSpanReaderV1_FindTraces(t *testing.T) {\n\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\ttraceID1 := model.NewTraceID(0, 1)\n\t\tspanID1 := model.NewSpanID(1)\n\t\ttraceID2 := model.NewTraceID(0, 2)\n\t\tspanID2 := model.NewSpanID(2)\n\t\ttrace1 := getTestingTrace(traceID1, spanID1)\n\t\ttrace2 := getTestingTrace(traceID2, spanID2)\n\t\ttraces := []dbmodel.Trace{trace1, trace2}\n\t\tm.On(\"FindTraces\", mock.Anything, mock.Anything).Return(traces, nil)\n\t\tactual, err := r.FindTraces(context.Background(), &spanstore.TraceQueryParameters{})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, actual, 2)\n\t\tassert.Len(t, actual[0].Spans, 1)\n\t\tassert.Len(t, actual[1].Spans, 1)\n\t\tassert.Equal(t, traceID1, actual[0].Spans[0].TraceID)\n\t\tassert.Equal(t, traceID2, actual[1].Spans[0].TraceID)\n\t})\n}\n\nfunc TestSpanReaderV1_FindTraceIDs(t *testing.T) {\n\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\ttraceId1Model := model.NewTraceID(0, 1)\n\t\ttraceId2Model := model.NewTraceID(0, 2)\n\t\ttraceId1 := dbmodel.TraceID(traceId1Model.String())\n\t\ttraceId2 := dbmodel.TraceID(traceId2Model.String())\n\t\ttraceIds := []dbmodel.TraceID{traceId1, traceId2}\n\t\tm.On(\"FindTraceIDs\", mock.Anything, mock.Anything).Return(traceIds, nil)\n\t\tactual, err := r.FindTraceIDs(context.Background(), &spanstore.TraceQueryParameters{})\n\t\trequire.NoError(t, err)\n\t\texpected := []model.TraceID{traceId1Model, traceId2Model}\n\t\tassert.Equal(t, expected, actual)\n\t})\n}\n\nfunc TestSpanReaderV1_FindTraceIDs_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\treturningTraceIDs []dbmodel.TraceID\n\t\treturningErr      error\n\t\texpectedError     string\n\t}{\n\t\t{\n\t\t\tname:          \"error from core span reader\",\n\t\t\treturningErr:  errors.New(\"error from core span reader\"),\n\t\t\texpectedError: \"error from core span reader\",\n\t\t},\n\t\t{\n\t\t\tname:              \"error from conversion\",\n\t\t\treturningTraceIDs: []dbmodel.TraceID{dbmodel.TraceID(\"wrong-id\")},\n\t\t\texpectedError:     \"making traceID from string 'wrong-id' failed: strconv.ParseUint: parsing \\\"wrong-id\\\": invalid syntax\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\t\t\tm.On(\"FindTraceIDs\", mock.Anything, mock.Anything).Return(tt.returningTraceIDs, tt.returningErr)\n\t\t\t\tactual, err := r.FindTraceIDs(context.Background(), &spanstore.TraceQueryParameters{})\n\t\t\t\tassert.Nil(t, actual)\n\t\t\t\tassert.ErrorContains(t, err, tt.expectedError)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestSpanReaderV1_GetServices(t *testing.T) {\n\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\tservices := []string{\"service-1\", \"service-2\"}\n\t\tm.On(\"GetServices\", mock.Anything).Return(services, nil)\n\t\tactual, err := r.GetServices(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, services, actual)\n\t})\n}\n\nfunc TestSpanReaderV1_GetOperations(t *testing.T) {\n\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\toperation := []dbmodel.Operation{{Name: \"operation-1\", SpanKind: \"kind-1\"}}\n\t\tinput := dbmodel.OperationQueryParameters{ServiceName: \"service\", SpanKind: \"kind-1\"}\n\t\tm.On(\"GetOperations\", mock.Anything, input).Return(operation, nil)\n\t\tactual, err := r.GetOperations(context.Background(), spanstore.OperationQueryParameters{ServiceName: \"service\", SpanKind: \"kind-1\"})\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, actual, 1)\n\t\tassert.Equal(t, operation[0].Name, actual[0].Name)\n\t\tassert.Equal(t, operation[0].SpanKind, actual[0].SpanKind)\n\t})\n}\n\nfunc TestSpanReaderV1_GetOperations_Error(t *testing.T) {\n\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\tinput := dbmodel.OperationQueryParameters{ServiceName: \"service\", SpanKind: \"kind-1\"}\n\t\tm.On(\"GetOperations\", mock.Anything, input).Return(nil, errors.New(\"error\"))\n\t\tactual, err := r.GetOperations(context.Background(), spanstore.OperationQueryParameters{ServiceName: \"service\", SpanKind: \"kind-1\"})\n\t\trequire.Error(t, err, \"error\")\n\t\tassert.Nil(t, actual)\n\t})\n}\n\ntype traceError struct {\n\tname            string\n\treturningErr    error\n\texpectedError   string\n\treturningTraces []dbmodel.Trace\n}\n\nfunc getTraceErrTests(includeTraceNotFound bool) []traceError {\n\ttests := []traceError{\n\t\t{\n\t\t\tname:            \"conversion error\",\n\t\t\texpectedError:   \"converting ES dbSpan to domain Span failed: strconv.ParseUint: parsing \\\"\\\": invalid syntax\",\n\t\t\treturningTraces: []dbmodel.Trace{getBadTrace()},\n\t\t},\n\t\t{\n\t\t\tname:          \"generic error\",\n\t\t\treturningErr:  errors.New(\"error\"),\n\t\t\texpectedError: \"error\",\n\t\t},\n\t}\n\tif includeTraceNotFound {\n\t\ttests = append(tests, traceError{\n\t\t\tname:            \"trace not found\",\n\t\t\treturningTraces: []dbmodel.Trace{},\n\t\t\texpectedError:   \"trace not found\",\n\t\t})\n\t}\n\treturn tests\n}\n\nfunc TestSpanReaderV1_GetTraceError(t *testing.T) {\n\ttests := getTraceErrTests(true)\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\t\t\tm.On(\"GetTraces\", mock.Anything, mock.Anything).Return(tt.returningTraces, tt.returningErr)\n\t\t\t\tquery := spanstore.GetTraceParameters{}\n\t\t\t\ttrace, err := r.GetTrace(context.Background(), query)\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedError)\n\t\t\t\trequire.Nil(t, trace)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestSpanReaderV1_FindTracesError(t *testing.T) {\n\ttests := getTraceErrTests(false)\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\twithSpanReaderV1(func(r *SpanReaderV1, m *mocks.CoreSpanReader) {\n\t\t\t\tm.On(\"FindTraces\", mock.Anything, mock.Anything).Return(tt.returningTraces, tt.returningErr)\n\t\t\t\tquery := &spanstore.TraceQueryParameters{}\n\t\t\t\ttrace, err := r.FindTraces(context.Background(), query)\n\t\t\t\trequire.Error(t, err, tt.expectedError)\n\t\t\t\trequire.Nil(t, trace)\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc getBadTrace() dbmodel.Trace {\n\treturn dbmodel.Trace{\n\t\tSpans: []dbmodel.Span{\n\t\t\t{\n\t\t\t\tOperationName: \"testing-operation\",\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestTraceIDsStringsToModelsConversion(t *testing.T) {\n\ttraceIDs, err := toModelTraceIDs([]dbmodel.TraceID{\"1\", \"2\", \"3\"})\n\trequire.NoError(t, err)\n\tassert.Len(t, traceIDs, 3)\n\tassert.Equal(t, model.NewTraceID(0, 1), traceIDs[0])\n\n\ttraceIDs, err = toModelTraceIDs([]dbmodel.TraceID{\"dsfjsdklfjdsofdfsdbfkgbgoaemlrksdfbsdofgerjl\"})\n\trequire.EqualError(t, err, \"making traceID from string 'dsfjsdklfjdsofdfsdbfkgbgoaemlrksdfbsdofgerjl' failed: TraceID cannot be longer than 32 hex characters: dsfjsdklfjdsofdfsdbfkgbgoaemlrksdfbsdofgerjl\")\n\tassert.Empty(t, traceIDs)\n}\n\nfunc TestConvertTraceIDsStringsToModels(t *testing.T) {\n\tids, err := toModelTraceIDs([]dbmodel.TraceID{\"1\", \"2\", \"01\", \"02\", \"001\", \"002\"})\n\trequire.NoError(t, err)\n\tassert.Equal(t, []model.TraceID{model.NewTraceID(0, 1), model.NewTraceID(0, 2)}, ids)\n\t_, err = toModelTraceIDs([]dbmodel.TraceID{\"blah\"})\n\trequire.ErrorContains(t, err, \"making traceID from string 'blah' failed: strconv.ParseUint: parsing \\\"blah\\\": invalid syntax\")\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/service_operation.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/cache\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\nconst (\n\tserviceName = \"serviceName\"\n\n\toperationsAggregation = \"distinct_operations\"\n\tservicesAggregation   = \"distinct_services\"\n)\n\n// ServiceOperationStorage stores service to operation pairs.\ntype ServiceOperationStorage struct {\n\tclient       func() es.Client\n\tlogger       *zap.Logger\n\tserviceCache cache.Cache\n}\n\n// NewServiceOperationStorage returns a new ServiceOperationStorage.\nfunc NewServiceOperationStorage(\n\tclient func() es.Client,\n\tlogger *zap.Logger,\n\tcacheTTL time.Duration,\n) *ServiceOperationStorage {\n\treturn &ServiceOperationStorage{\n\t\tclient: client,\n\t\tlogger: logger,\n\t\tserviceCache: cache.NewLRUWithOptions(\n\t\t\t100000,\n\t\t\t&cache.Options{\n\t\t\t\tTTL: cacheTTL,\n\t\t\t},\n\t\t),\n\t}\n}\n\n// Write saves a service to operation pair.\nfunc (s *ServiceOperationStorage) Write(indexName string, jsonSpan *dbmodel.Span) {\n\t// Insert serviceName:operationName document\n\tservice := dbmodel.Service{\n\t\tServiceName:   jsonSpan.Process.ServiceName,\n\t\tOperationName: jsonSpan.OperationName,\n\t}\n\n\tcacheKey := hashCode(service)\n\tif !keyInCache(cacheKey, s.serviceCache) {\n\t\ts.client().Index().Index(indexName).Type(serviceType).Id(cacheKey).BodyJson(service).Add()\n\t\twriteCache(cacheKey, s.serviceCache)\n\t}\n}\n\nfunc (s *ServiceOperationStorage) getServices(ctx context.Context, indices []string, maxDocCount int) ([]string, error) {\n\tserviceAggregation := getServicesAggregation(maxDocCount)\n\n\tsearchService := s.client().Search(indices...).\n\t\tSize(0). // set to 0 because we don't want actual documents.\n\t\tIgnoreUnavailable(true).\n\t\tAggregation(servicesAggregation, serviceAggregation)\n\n\tsearchResult, err := searchService.Do(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"search services failed: %w\", es.DetailedError(err))\n\t}\n\tif searchResult.Aggregations == nil {\n\t\treturn []string{}, nil\n\t}\n\tbucket, found := searchResult.Aggregations.Terms(servicesAggregation)\n\tif !found {\n\t\treturn nil, errors.New(\"could not find aggregation of \" + servicesAggregation)\n\t}\n\tserviceNamesBucket := bucket.Buckets\n\treturn bucketToStringArray[string](serviceNamesBucket)\n}\n\nfunc getServicesAggregation(maxDocCount int) elastic.Query {\n\treturn elastic.NewTermsAggregation().\n\t\tField(serviceName).\n\t\tSize(maxDocCount) // ES deprecated size omission for aggregating all. https://github.com/elastic/elasticsearch/issues/18838\n}\n\nfunc (s *ServiceOperationStorage) getOperations(ctx context.Context, indices []string, service string, maxDocCount int) ([]string, error) {\n\tserviceQuery := elastic.NewTermQuery(serviceName, service)\n\tserviceFilter := getOperationsAggregation(maxDocCount)\n\n\tsearchService := s.client().Search(indices...).\n\t\tSize(0).\n\t\tQuery(serviceQuery).\n\t\tIgnoreUnavailable(true).\n\t\tAggregation(operationsAggregation, serviceFilter)\n\n\tsearchResult, err := searchService.Do(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"search operations failed: %w\", es.DetailedError(err))\n\t}\n\tif searchResult.Aggregations == nil {\n\t\treturn []string{}, nil\n\t}\n\tbucket, found := searchResult.Aggregations.Terms(operationsAggregation)\n\tif !found {\n\t\treturn nil, errors.New(\"could not find aggregation of \" + operationsAggregation)\n\t}\n\toperationNamesBucket := bucket.Buckets\n\treturn bucketToStringArray[string](operationNamesBucket)\n}\n\nfunc getOperationsAggregation(maxDocCount int) elastic.Query {\n\treturn elastic.NewTermsAggregation().\n\t\tField(operationNameField).\n\t\tSize(maxDocCount) // ES deprecated size omission for aggregating all. https://github.com/elastic/elasticsearch/issues/18838\n}\n\nfunc hashCode(s dbmodel.Service) string {\n\th := fnv.New64a()\n\th.Write([]byte(s.ServiceName))\n\th.Write([]byte(s.OperationName))\n\treturn strconv.FormatUint(h.Sum64(), 16)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/service_operation_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n)\n\nfunc TestWriteService(t *testing.T) {\n\twithSpanWriter(func(w *spanWriterTest) {\n\t\tindexService := &mocks.IndexService{}\n\n\t\tindexName := \"jaeger-1995-04-21\"\n\t\tserviceHash := \"de3b5a8f1a79989d\"\n\n\t\tindexService.On(\"Index\", stringMatcher(indexName)).Return(indexService)\n\t\tindexService.On(\"Type\", stringMatcher(serviceType)).Return(indexService)\n\t\tindexService.On(\"Id\", stringMatcher(serviceHash)).Return(indexService)\n\t\tindexService.On(\"BodyJson\", mock.AnythingOfType(\"dbmodel.Service\")).Return(indexService)\n\t\tindexService.On(\"Add\")\n\n\t\tw.client.On(\"Index\").Return(indexService)\n\n\t\tjsonSpan := &dbmodel.Span{\n\t\t\tTraceID:       dbmodel.TraceID(\"1\"),\n\t\t\tSpanID:        dbmodel.SpanID(\"0\"),\n\t\t\tOperationName: \"operation\",\n\t\t\tProcess: dbmodel.Process{\n\t\t\t\tServiceName: \"service\",\n\t\t\t},\n\t\t}\n\n\t\tw.writer.writeService(indexName, jsonSpan)\n\n\t\tindexService.AssertNumberOfCalls(t, \"Add\", 1)\n\t\tassert.Empty(t, w.logBuffer.String())\n\n\t\t// test that cache works, will call the index service only once.\n\t\tw.writer.writeService(indexName, jsonSpan)\n\t\tindexService.AssertNumberOfCalls(t, \"Add\", 1)\n\t})\n}\n\nfunc TestWriteServiceError(*testing.T) {\n\twithSpanWriter(func(w *spanWriterTest) {\n\t\tindexService := &mocks.IndexService{}\n\n\t\tindexName := \"jaeger-1995-04-21\"\n\t\tserviceHash := \"de3b5a8f1a79989d\"\n\n\t\tindexService.On(\"Index\", stringMatcher(indexName)).Return(indexService)\n\t\tindexService.On(\"Type\", stringMatcher(serviceType)).Return(indexService)\n\t\tindexService.On(\"Id\", stringMatcher(serviceHash)).Return(indexService)\n\t\tindexService.On(\"BodyJson\", mock.AnythingOfType(\"dbmodel.Service\")).Return(indexService)\n\t\tindexService.On(\"Add\")\n\n\t\tw.client.On(\"Index\").Return(indexService)\n\n\t\tjsonSpan := &dbmodel.Span{\n\t\t\tTraceID:       dbmodel.TraceID(\"1\"),\n\t\t\tSpanID:        dbmodel.SpanID(\"0\"),\n\t\t\tOperationName: \"operation\",\n\t\t\tProcess: dbmodel.Process{\n\t\t\t\tServiceName: \"service\",\n\t\t\t},\n\t\t}\n\n\t\tw.writer.writeService(indexName, jsonSpan)\n\t})\n}\n\nfunc TestSpanReader_GetServices(t *testing.T) {\n\ttestGet(servicesAggregation, t)\n}\n\nfunc TestSpanReader_GetOperations(t *testing.T) {\n\ttestGet(operationsAggregation, t)\n}\n\nfunc TestSpanReader_GetServicesEmptyIndex(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t}, nil)\n\t\tservices, err := r.reader.GetServices(context.Background())\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, services)\n\t})\n}\n\nfunc TestSpanReader_GetOperationsEmptyIndex(t *testing.T) {\n\twithSpanReader(t, func(r *spanReaderTest) {\n\t\tmockSearchService(r).\n\t\t\tReturn(&elastic.SearchResult{}, nil)\n\t\tmockMultiSearchService(r).\n\t\t\tReturn(&elastic.MultiSearchResult{\n\t\t\t\tResponses: []*elastic.SearchResult{},\n\t\t\t}, nil)\n\t\tservices, err := r.reader.GetOperations(\n\t\t\tcontext.Background(),\n\t\t\tdbmodel.OperationQueryParameters{ServiceName: \"foo\"},\n\t\t)\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, services)\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/to_domain.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\n// NewToDomain creates ToDomain\nfunc NewToDomain() ToDomain {\n\treturn ToDomain{}\n}\n\n// ToDomain is used to convert Span to model.Span\ntype ToDomain struct{}\n\n// SpanToDomain converts db span into model Span\nfunc (td ToDomain) SpanToDomain(dbSpan *dbmodel.Span) (*model.Span, error) {\n\ttags, err := td.convertKeyValues(dbSpan.Tags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogs, err := td.convertLogs(dbSpan.Logs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trefs, err := td.convertRefs(dbSpan.References)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprocess, err := td.convertProcess(dbSpan.Process)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttraceID, err := model.TraceIDFromString(string(dbSpan.TraceID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tspanIDInt, err := model.SpanIDFromString(string(dbSpan.SpanID))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif dbSpan.ParentSpanID != \"\" {\n\t\tparentSpanID, err := model.SpanIDFromString(string(dbSpan.ParentSpanID))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trefs = model.MaybeAddParentSpanID(traceID, parentSpanID, refs)\n\t}\n\tspan := &model.Span{\n\t\tTraceID:       traceID,\n\t\tSpanID:        model.NewSpanID(uint64(spanIDInt)),\n\t\tOperationName: dbSpan.OperationName,\n\t\tReferences:    refs,\n\t\tFlags:         model.Flags(uint32(dbSpan.Flags)),\n\t\tStartTime:     model.EpochMicrosecondsAsTime(dbSpan.StartTime),\n\t\tDuration:      model.MicrosecondsAsDuration(dbSpan.Duration),\n\t\tTags:          tags,\n\t\tLogs:          logs,\n\t\tProcess:       process,\n\t}\n\treturn span, nil\n}\n\nfunc (ToDomain) convertRefs(refs []dbmodel.Reference) ([]model.SpanRef, error) {\n\tretMe := make([]model.SpanRef, len(refs))\n\tfor i, r := range refs {\n\t\t// There are some inconsistencies with ReferenceTypes, hence the hacky fix.\n\t\tvar refType model.SpanRefType\n\t\tswitch r.RefType {\n\t\tcase dbmodel.ChildOf:\n\t\t\trefType = model.ChildOf\n\t\tcase dbmodel.FollowsFrom:\n\t\t\trefType = model.FollowsFrom\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"not a valid SpanRefType string %s\", string(r.RefType))\n\t\t}\n\n\t\ttraceID, err := model.TraceIDFromString(string(r.TraceID))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tspanID, err := strconv.ParseUint(string(r.SpanID), 16, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tretMe[i] = model.SpanRef{\n\t\t\tRefType: refType,\n\t\t\tTraceID: traceID,\n\t\t\tSpanID:  model.NewSpanID(spanID),\n\t\t}\n\t}\n\treturn retMe, nil\n}\n\nfunc (td ToDomain) convertKeyValues(tags []dbmodel.KeyValue) ([]model.KeyValue, error) {\n\tretMe := make([]model.KeyValue, len(tags))\n\tfor i := range tags {\n\t\tkv, err := td.convertKeyValue(&tags[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tretMe[i] = kv\n\t}\n\treturn retMe, nil\n}\n\n// convertKeyValue expects the Value field to be string, because it only works\n// as a reverse transformation after FromDomain() for ElasticSearch model.\nfunc (td ToDomain) convertKeyValue(tag *dbmodel.KeyValue) (model.KeyValue, error) {\n\tif tag.Value == nil {\n\t\treturn model.KeyValue{}, fmt.Errorf(\"invalid nil Value in %v\", tag)\n\t}\n\ttagValue, ok := tag.Value.(string)\n\tif !ok {\n\t\tswitch tag.Type {\n\t\tcase dbmodel.Int64Type, dbmodel.Float64Type:\n\t\t\tkv, err := td.fromDBNumber(tag)\n\t\t\tif err != nil {\n\t\t\t\treturn model.KeyValue{}, err\n\t\t\t}\n\t\t\treturn kv, nil\n\t\tcase dbmodel.BoolType:\n\t\t\tif boolVal, ok := tag.Value.(bool); ok {\n\t\t\t\treturn model.Bool(tag.Key, boolVal), nil\n\t\t\t}\n\t\t\treturn model.KeyValue{}, invalidValueErr(tag)\n\t\t// string and binary values should always be of string type\n\t\tdefault:\n\t\t\treturn model.KeyValue{}, invalidValueErr(tag)\n\t\t}\n\t}\n\tswitch tag.Type {\n\tcase dbmodel.StringType:\n\t\treturn model.String(tag.Key, tagValue), nil\n\tcase dbmodel.BoolType:\n\t\tvalue, err := strconv.ParseBool(tagValue)\n\t\tif err != nil {\n\t\t\treturn model.KeyValue{}, err\n\t\t}\n\t\treturn model.Bool(tag.Key, value), nil\n\tcase dbmodel.Int64Type:\n\t\tvalue, err := strconv.ParseInt(tagValue, 10, 64)\n\t\tif err != nil {\n\t\t\treturn model.KeyValue{}, err\n\t\t}\n\t\treturn model.Int64(tag.Key, value), nil\n\tcase dbmodel.Float64Type:\n\t\tvalue, err := strconv.ParseFloat(tagValue, 64)\n\t\tif err != nil {\n\t\t\treturn model.KeyValue{}, err\n\t\t}\n\t\treturn model.Float64(tag.Key, value), nil\n\tcase dbmodel.BinaryType:\n\t\tvalue, err := hex.DecodeString(tagValue)\n\t\tif err != nil {\n\t\t\treturn model.KeyValue{}, err\n\t\t}\n\t\treturn model.Binary(tag.Key, value), nil\n\tdefault:\n\t\treturn model.KeyValue{}, fmt.Errorf(\"not a valid ValueType string %s\", string(tag.Type))\n\t}\n}\n\nfunc (ToDomain) fromDBNumber(kv *dbmodel.KeyValue) (model.KeyValue, error) {\n\tswitch kv.Type {\n\tcase dbmodel.Int64Type:\n\t\tswitch v := kv.Value.(type) {\n\t\tcase int64:\n\t\t\treturn model.Int64(kv.Key, v), nil\n\t\t// This case is very much possible as JSON converts every number to float64\n\t\tcase float64:\n\t\t\treturn model.Int64(kv.Key, int64(v)), nil\n\t\tcase json.Number:\n\t\t\tn, err := v.Int64()\n\t\t\tif err == nil {\n\t\t\t\treturn model.Int64(kv.Key, n), nil\n\t\t\t}\n\t\t\treturn model.KeyValue{}, fmt.Errorf(\"not a valid number ValueType %s\", string(kv.Type))\n\t\tdefault:\n\t\t\treturn model.KeyValue{}, invalidValueErr(kv)\n\t\t}\n\tcase dbmodel.Float64Type:\n\t\tswitch v := kv.Value.(type) {\n\t\tcase float64:\n\t\t\treturn model.Float64(kv.Key, v), nil\n\t\tcase json.Number:\n\t\t\tn, err := v.Float64()\n\t\t\tif err == nil {\n\t\t\t\treturn model.Float64(kv.Key, n), nil\n\t\t\t}\n\t\t\treturn model.KeyValue{}, fmt.Errorf(\"not a valid number ValueType %s\", string(kv.Type))\n\t\tdefault:\n\t\t\treturn model.KeyValue{}, invalidValueErr(kv)\n\t\t}\n\tdefault:\n\t\treturn model.KeyValue{}, fmt.Errorf(\"not a valid number ValueType %s\", string(kv.Type))\n\t}\n}\n\nfunc invalidValueErr(kv *dbmodel.KeyValue) error {\n\treturn fmt.Errorf(\"invalid %s type in %+v\", string(kv.Type), kv.Value)\n}\n\nfunc (td ToDomain) convertLogs(logs []dbmodel.Log) ([]model.Log, error) {\n\tretMe := make([]model.Log, len(logs))\n\tfor i, l := range logs {\n\t\tfields, err := td.convertKeyValues(l.Fields)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tretMe[i] = model.Log{\n\t\t\tTimestamp: model.EpochMicrosecondsAsTime(l.Timestamp),\n\t\t\tFields:    fields,\n\t\t}\n\t}\n\treturn retMe, nil\n}\n\nfunc (td ToDomain) convertProcess(process dbmodel.Process) (*model.Process, error) {\n\ttags, err := td.convertKeyValues(process.Tags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &model.Process{\n\t\tTags:        tags,\n\t\tServiceName: process.ServiceName,\n\t}, nil\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/to_domain_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"testing\"\n\n\tgogojsonpb \"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\nfunc TestToDomain(t *testing.T) {\n\trunToDomainTest(t, false)\n\trunToDomainTest(t, true)\n\t// this is just to confirm the uint64 representation of float64(72.5) used as a \"temperature\" tag\n\tassert.Equal(t, int64(4634802150889750528), int64(math.Float64bits(72.5)))\n}\n\nfunc runToDomainTest(t *testing.T, testParentSpanID bool) {\n\tfor i := 1; i <= NumberOfFixtures; i++ {\n\t\tspan, err := loadESSpanFixture(i)\n\t\trequire.NoError(t, err)\n\t\tif testParentSpanID {\n\t\t\tspan.ParentSpanID = \"3\"\n\t\t}\n\n\t\tactualSpan, err := NewToDomain().SpanToDomain(&span)\n\t\trequire.NoError(t, err)\n\n\t\tout := fmt.Sprintf(\"fixtures/domain_%02d.json\", i)\n\t\toutStr, err := os.ReadFile(out)\n\t\trequire.NoError(t, err)\n\t\tvar expectedSpan model.Span\n\t\trequire.NoError(t, gogojsonpb.Unmarshal(bytes.NewReader(outStr), &expectedSpan))\n\n\t\tCompareModelSpans(t, &expectedSpan, actualSpan)\n\t}\n}\n\nfunc loadESSpanFixture(i int) (dbmodel.Span, error) {\n\tin := fmt.Sprintf(\"fixtures/es_%02d.json\", i)\n\tinStr, err := os.ReadFile(in)\n\tif err != nil {\n\t\treturn dbmodel.Span{}, err\n\t}\n\tvar span dbmodel.Span\n\terr = json.Unmarshal(inStr, &span)\n\treturn span, err\n}\n\nfunc failingSpanTransform(t *testing.T, embeddedSpan *dbmodel.Span, errMsg string) {\n\tdomainSpan, err := NewToDomain().SpanToDomain(embeddedSpan)\n\tassert.Nil(t, domainSpan)\n\trequire.EqualError(t, err, errMsg)\n}\n\nfunc failingSpanTransformAnyMsg(t *testing.T, embeddedSpan *dbmodel.Span) {\n\tdomainSpan, err := NewToDomain().SpanToDomain(embeddedSpan)\n\tassert.Nil(t, domainSpan)\n\trequire.Error(t, err)\n}\n\nfunc TestFailureBadTypeTags(t *testing.T) {\n\tbadTagESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\n\tbadTagESSpan.Tags = []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"meh\",\n\t\t\tType:  \"badType\",\n\t\t\tValue: \"\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badTagESSpan)\n}\n\nfunc TestFailureBadBoolTags(t *testing.T) {\n\tbadTagESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\n\tbadTagESSpan.Tags = []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"meh\",\n\t\t\tValue: \"meh\",\n\t\t\tType:  \"bool\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badTagESSpan)\n}\n\nfunc TestFailureBadIntTags(t *testing.T) {\n\tbadTagESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\n\tbadTagESSpan.Tags = []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"meh\",\n\t\t\tValue: \"meh\",\n\t\t\tType:  \"int64\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badTagESSpan)\n}\n\nfunc TestFailureBadFloatTags(t *testing.T) {\n\tbadTagESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\n\tbadTagESSpan.Tags = []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"meh\",\n\t\t\tValue: \"meh\",\n\t\t\tType:  \"float64\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badTagESSpan)\n}\n\nfunc TestFailureBadBinaryTags(t *testing.T) {\n\tbadTagESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\n\tbadTagESSpan.Tags = []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"zzzz\",\n\t\t\tValue: \"zzzz\",\n\t\t\tType:  \"binary\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badTagESSpan)\n}\n\nfunc TestFailureBadLogs(t *testing.T) {\n\tbadLogsESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadLogsESSpan.Logs = []dbmodel.Log{\n\t\t{\n\t\t\tTimestamp: 0,\n\t\t\tFields: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"sneh\",\n\t\t\t\t\tValue: \"\",\n\t\t\t\t\tType:  \"badType\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfailingSpanTransform(t, &badLogsESSpan, \"not a valid ValueType string badType\")\n}\n\nfunc TestRevertKeyValueOfType(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tkv    *dbmodel.KeyValue\n\t\terr   string\n\t\toutKv model.KeyValue\n\t}{\n\t\t{\n\t\t\tname: \"not a valid ValueType string\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"sneh\",\n\t\t\t\tType:  \"badType\",\n\t\t\t\tValue: \"someString\",\n\t\t\t},\n\t\t\terr: \"not a valid ValueType string\",\n\t\t},\n\t\t{\n\t\t\tname: \"invalid nil Value\",\n\t\t\tkv:   &dbmodel.KeyValue{},\n\t\t\terr:  \"invalid nil Value\",\n\t\t},\n\t\t{\n\t\t\tname: \"right int value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: int64(123),\n\t\t\t},\n\t\t\toutKv: model.KeyValue{\n\t\t\t\tKey:    \"int-val\",\n\t\t\t\tVInt64: 123,\n\t\t\t\tVType:  2,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right int float value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: float64(123),\n\t\t\t},\n\t\t\toutKv: model.KeyValue{\n\t\t\t\tKey:    \"int-val\",\n\t\t\t\tVInt64: 123,\n\t\t\t\tVType:  2,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right int json number\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: json.Number(\"123\"),\n\t\t\t},\n\t\t\toutKv: model.KeyValue{\n\t\t\t\tKey:    \"int-val\",\n\t\t\t\tVInt64: 123,\n\t\t\t\tVType:  2,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right float value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"float-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: 123.4,\n\t\t\t},\n\t\t\toutKv: model.KeyValue{\n\t\t\t\tKey:      \"float-val\",\n\t\t\t\tVFloat64: 123.4,\n\t\t\t\tVType:    3,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right float json number\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"float-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: json.Number(\"123.4\"),\n\t\t\t},\n\t\t\toutKv: model.KeyValue{\n\t\t\t\tKey:      \"float-val\",\n\t\t\t\tVFloat64: 123.4,\n\t\t\t\tVType:    3,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong int64 value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\terr: \"invalid int64 type in true\",\n\t\t},\n\t\t{\n\t\t\tname: \"wrong float64 value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"float-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\terr: \"invalid float64 type in true\",\n\t\t},\n\t\t{\n\t\t\tname: \"wrong bool value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"bool-val\",\n\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\tValue: 1.23,\n\t\t\t},\n\t\t\terr: \"invalid bool type in 1.23\",\n\t\t},\n\t\t{\n\t\t\tname: \"wrong string value\",\n\t\t\tkv: &dbmodel.KeyValue{\n\t\t\t\tKey:   \"string-val\",\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: 123,\n\t\t\t},\n\t\t\terr: \"invalid string type in 123\",\n\t\t},\n\t}\n\ttd := ToDomain{}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttag := test.kv\n\t\t\tout, err := td.convertKeyValue(tag)\n\t\t\tif test.err != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.outKv, out)\n\t\t})\n\t}\n}\n\nfunc TestFromDBTag_DefaultCase(t *testing.T) {\n\ttag := &dbmodel.KeyValue{\n\t\tKey:   \"test-key\",\n\t\tType:  \"unknown-type\",\n\t\tValue: \"test-value\",\n\t}\n\n\ttd := ToDomain{}\n\tresult, err := td.convertKeyValue(tag)\n\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"not a valid ValueType string unknown-type\")\n\tassert.Equal(t, model.KeyValue{}, result)\n}\n\nfunc TestFailureBadRefs(t *testing.T) {\n\tbadRefsESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadRefsESSpan.References = []dbmodel.Reference{\n\t\t{\n\t\t\tRefType: \"makeOurOwnCasino\",\n\t\t\tTraceID: \"1\",\n\t\t},\n\t}\n\tfailingSpanTransform(t, &badRefsESSpan, \"not a valid SpanRefType string makeOurOwnCasino\")\n}\n\nfunc TestFailureBadTraceIDRefs(t *testing.T) {\n\tbadRefsESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadRefsESSpan.References = []dbmodel.Reference{\n\t\t{\n\t\t\tRefType: \"CHILD_OF\",\n\t\t\tTraceID: \"ZZBADZZ\",\n\t\t\tSpanID:  \"1\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badRefsESSpan)\n}\n\nfunc TestFailureBadSpanIDRefs(t *testing.T) {\n\tbadRefsESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadRefsESSpan.References = []dbmodel.Reference{\n\t\t{\n\t\t\tRefType: \"CHILD_OF\",\n\t\t\tTraceID: \"1\",\n\t\t\tSpanID:  \"ZZBADZZ\",\n\t\t},\n\t}\n\tfailingSpanTransformAnyMsg(t, &badRefsESSpan)\n}\n\nfunc TestFailureBadProcess(t *testing.T) {\n\tbadProcessESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\n\tbadTags := []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"meh\",\n\t\t\tValue: \"\",\n\t\t\tType:  \"badType\",\n\t\t},\n\t}\n\tbadProcessESSpan.Process = dbmodel.Process{\n\t\tServiceName: \"hello\",\n\t\tTags:        badTags,\n\t}\n\tfailingSpanTransform(t, &badProcessESSpan, \"not a valid ValueType string badType\")\n}\n\nfunc TestFailureBadTraceID(t *testing.T) {\n\tbadTraceIDESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadTraceIDESSpan.TraceID = \"zz\"\n\tfailingSpanTransformAnyMsg(t, &badTraceIDESSpan)\n}\n\nfunc TestFailureBadSpanID(t *testing.T) {\n\tbadSpanIDESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadSpanIDESSpan.SpanID = \"zz\"\n\tfailingSpanTransformAnyMsg(t, &badSpanIDESSpan)\n}\n\nfunc TestFailureBadParentSpanID(t *testing.T) {\n\tbadParentSpanIDESSpan, err := loadESSpanFixture(1)\n\trequire.NoError(t, err)\n\tbadParentSpanIDESSpan.ParentSpanID = \"zz\"\n\tfailingSpanTransformAnyMsg(t, &badParentSpanIDESSpan)\n}\n\nfunc CompareModelSpans(t *testing.T, expected *model.Span, actual *model.Span) {\n\tmodel.SortSpan(expected)\n\tmodel.SortSpan(actual)\n\n\tif !assert.Equal(t, expected, actual) {\n\t\tfor _, err := range pretty.Diff(expected, actual) {\n\t\t\tt.Log(err)\n\t\t}\n\t\tout, err := json.Marshal(actual)\n\t\trequire.NoError(t, err)\n\t\tt.Logf(\"Actual trace: %s\", string(out))\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/writer.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/cache\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\tcfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/spanstoremetrics\"\n)\n\nconst (\n\tspanType               = \"span\"\n\tserviceType            = \"service\"\n\tserviceCacheTTLDefault = 12 * time.Hour\n\tindexCacheTTLDefault   = 48 * time.Hour\n)\n\ntype serviceWriter func(string, *dbmodel.Span)\n\n// SpanWriter is a wrapper around elastic.Client\ntype SpanWriter struct {\n\tclient func() es.Client\n\tlogger *zap.Logger\n\t// indexCache       cache.Cache\n\twriterMetrics     *spanstoremetrics.WriteMetrics\n\tserviceWriter     serviceWriter\n\tspanServiceIndex  spanAndServiceIndexFn\n\tallTagsAsFields   bool\n\ttagDotReplacement string\n\ttagKeysAsFields   map[string]bool\n}\n\n// CoreSpanWriter is a DB-Level abstraction which directly deals with database level operations\ntype CoreSpanWriter interface {\n\t// WriteSpan writes a span and its corresponding service:operation in ElasticSearch\n\tWriteSpan(spanStartTime time.Time, span *dbmodel.Span)\n\t// Close closes CoreSpanWriter\n\tClose() error\n}\n\n// SpanWriterParams holds constructor parameters for NewSpanWriter\ntype SpanWriterParams struct {\n\tClient              func() es.Client\n\tLogger              *zap.Logger\n\tMetricsFactory      metrics.Factory\n\tSpanIndex           cfg.IndexOptions\n\tServiceIndex        cfg.IndexOptions\n\tIndexPrefix         cfg.IndexPrefix\n\tAllTagsAsFields     bool\n\tTagKeysAsFields     []string\n\tTagDotReplacement   string\n\tUseReadWriteAliases bool\n\tWriteAliasSuffix    string\n\tSpanWriteAlias      string\n\tServiceWriteAlias   string\n\tServiceCacheTTL     time.Duration\n}\n\n// NewSpanWriter creates a new SpanWriter for use\nfunc NewSpanWriter(p SpanWriterParams) *SpanWriter {\n\tserviceCacheTTL := p.ServiceCacheTTL\n\tif p.ServiceCacheTTL == 0 {\n\t\tserviceCacheTTL = serviceCacheTTLDefault\n\t}\n\n\twriteAliasSuffix := \"\"\n\tif p.UseReadWriteAliases {\n\t\tif p.WriteAliasSuffix != \"\" {\n\t\t\twriteAliasSuffix = p.WriteAliasSuffix\n\t\t} else {\n\t\t\twriteAliasSuffix = \"write\"\n\t\t}\n\t}\n\n\ttags := map[string]bool{}\n\tfor _, k := range p.TagKeysAsFields {\n\t\ttags[k] = true\n\t}\n\n\tserviceOperationStorage := NewServiceOperationStorage(p.Client, p.Logger, serviceCacheTTL)\n\treturn &SpanWriter{\n\t\tclient:            p.Client,\n\t\tlogger:            p.Logger,\n\t\twriterMetrics:     spanstoremetrics.NewWriter(p.MetricsFactory, \"spans\"),\n\t\tserviceWriter:     serviceOperationStorage.Write,\n\t\tspanServiceIndex:  getSpanAndServiceIndexFn(p, writeAliasSuffix),\n\t\ttagKeysAsFields:   tags,\n\t\tallTagsAsFields:   p.AllTagsAsFields,\n\t\ttagDotReplacement: p.TagDotReplacement,\n\t}\n}\n\n// spanAndServiceIndexFn returns names of span and service indices\ntype spanAndServiceIndexFn func(spanTime time.Time) (string, string)\n\nfunc getSpanAndServiceIndexFn(p SpanWriterParams, writeAlias string) spanAndServiceIndexFn {\n\t// If explicit write aliases are provided, use them directly without modification\n\tif p.SpanWriteAlias != \"\" && p.ServiceWriteAlias != \"\" {\n\t\treturn func(_ time.Time) (string, string) {\n\t\t\treturn p.SpanWriteAlias, p.ServiceWriteAlias\n\t\t}\n\t}\n\n\t// Otherwise, use the standard prefix + suffix approach\n\tspanIndexPrefix := p.IndexPrefix.Apply(spanIndexBaseName)\n\tserviceIndexPrefix := p.IndexPrefix.Apply(serviceIndexBaseName)\n\n\tif p.UseReadWriteAliases {\n\t\treturn func(_ time.Time) (string, string) {\n\t\t\treturn spanIndexPrefix + writeAlias, serviceIndexPrefix + writeAlias\n\t\t}\n\t}\n\n\treturn func(date time.Time) (string, string) {\n\t\treturn indexWithDate(spanIndexPrefix, p.SpanIndex.DateLayout, date), indexWithDate(serviceIndexPrefix, p.ServiceIndex.DateLayout, date)\n\t}\n}\n\n// WriteSpan writes a span and its corresponding service:operation in ElasticSearch\nfunc (s *SpanWriter) WriteSpan(spanStartTime time.Time, span *dbmodel.Span) {\n\ts.writerMetrics.Attempts.Inc(1)\n\ts.convertNestedTagsToFieldTags(span)\n\tspanIndexName, serviceIndexName := s.spanServiceIndex(spanStartTime)\n\tif serviceIndexName != \"\" {\n\t\ts.writeService(serviceIndexName, span)\n\t}\n\ts.writeSpanToIndex(spanIndexName, span)\n\ts.logger.Debug(\"Wrote span to ES index\", zap.String(\"index\", spanIndexName))\n}\n\nfunc (s *SpanWriter) convertNestedTagsToFieldTags(span *dbmodel.Span) {\n\tprocessNestedTags, processFieldTags := s.splitElevatedTags(span.Process.Tags)\n\tspan.Process.Tags = processNestedTags\n\tspan.Process.Tag = processFieldTags\n\tnestedTags, fieldTags := s.splitElevatedTags(span.Tags)\n\tspan.Tags = nestedTags\n\tspan.Tag = fieldTags\n}\n\n// Close closes SpanWriter\nfunc (s *SpanWriter) Close() error {\n\treturn s.client().Close()\n}\n\nfunc keyInCache(key string, c cache.Cache) bool {\n\treturn c.Get(key) != nil\n}\n\nfunc writeCache(key string, c cache.Cache) {\n\tc.Put(key, key)\n}\n\nfunc (s *SpanWriter) writeService(indexName string, jsonSpan *dbmodel.Span) {\n\ts.serviceWriter(indexName, jsonSpan)\n}\n\nfunc (s *SpanWriter) writeSpanToIndex(indexName string, jsonSpan *dbmodel.Span) {\n\ts.client().Index().Index(indexName).Type(spanType).BodyJson(&jsonSpan).Add()\n}\n\nfunc (s *SpanWriter) splitElevatedTags(keyValues []dbmodel.KeyValue) ([]dbmodel.KeyValue, map[string]any) {\n\tif !s.allTagsAsFields && len(s.tagKeysAsFields) == 0 {\n\t\treturn keyValues, nil\n\t}\n\tvar tagsMap map[string]any\n\tvar kvs []dbmodel.KeyValue\n\tfor _, kv := range keyValues {\n\t\tif kv.Type != dbmodel.BinaryType && (s.allTagsAsFields || s.tagKeysAsFields[kv.Key]) {\n\t\t\tif tagsMap == nil {\n\t\t\t\ttagsMap = map[string]any{}\n\t\t\t}\n\t\t\ttagsMap[strings.ReplaceAll(kv.Key, \".\", s.tagDotReplacement)] = kv.Value\n\t\t} else {\n\t\t\tkvs = append(kvs, kv)\n\t\t}\n\t}\n\tif kvs == nil {\n\t\tkvs = make([]dbmodel.KeyValue, 0)\n\t}\n\treturn kvs, tagsMap\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/writer_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\ntype spanWriterTest struct {\n\tclient    *mocks.Client\n\tlogger    *zap.Logger\n\tlogBuffer *testutils.Buffer\n\twriter    *SpanWriter\n}\n\nfunc withSpanWriter(fn func(w *spanWriterTest)) {\n\tclient := &mocks.Client{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\tw := &spanWriterTest{\n\t\tclient:    client,\n\t\tlogger:    logger,\n\t\tlogBuffer: logBuffer,\n\t\twriter: NewSpanWriter(SpanWriterParams{\n\t\t\tClient: func() es.Client { return client },\n\t\t\tLogger: logger, MetricsFactory: metricsFactory,\n\t\t\tSpanIndex:    config.IndexOptions{DateLayout: \"2006-01-02\"},\n\t\t\tServiceIndex: config.IndexOptions{DateLayout: \"2006-01-02\"},\n\t\t}),\n\t}\n\tfn(w)\n}\n\nvar _ spanstore.Writer = &SpanWriterV1{} // check API conformance\n\nfunc TestSpanWriterIndices(t *testing.T) {\n\tclient := &mocks.Client{}\n\tclientFn := func() es.Client { return client }\n\tlogger, _ := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\tdate := time.Now()\n\tspanDataLayout := \"2006-01-02-15\"\n\tserviceDataLayout := \"2006-01-02\"\n\tspanDataLayoutFormat := date.UTC().Format(spanDataLayout)\n\tserviceDataLayoutFormat := date.UTC().Format(serviceDataLayout)\n\n\tspanIndexOpts := config.IndexOptions{DateLayout: spanDataLayout}\n\tserviceIndexOpts := config.IndexOptions{DateLayout: serviceDataLayout}\n\n\ttestCases := []struct {\n\t\tindices []string\n\t\tparams  SpanWriterParams\n\t}{\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts,\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + spanDataLayoutFormat, serviceIndexBaseName + serviceDataLayoutFormat},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, UseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + \"write\", serviceIndexBaseName + \"write\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts,\n\t\t\t\tWriteAliasSuffix: \"archive\", // ignored because UseReadWriteAliases is false\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + spanDataLayoutFormat, serviceIndexBaseName + serviceDataLayoutFormat},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, IndexPrefix: \"foo:\",\n\t\t\t},\n\t\t\tindices: []string{\"foo:\" + config.IndexPrefixSeparator + spanIndexBaseName + spanDataLayoutFormat, \"foo:\" + config.IndexPrefixSeparator + serviceIndexBaseName + serviceDataLayoutFormat},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, IndexPrefix: \"foo:\", UseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{\"foo:-\" + spanIndexBaseName + \"write\", \"foo:-\" + serviceIndexBaseName + \"write\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, WriteAliasSuffix: \"archive\", UseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{spanIndexBaseName + \"archive\", serviceIndexBaseName + \"archive\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, IndexPrefix: \"foo:\", WriteAliasSuffix: \"archive\", UseReadWriteAliases: true,\n\t\t\t},\n\t\t\tindices: []string{\"foo:\" + config.IndexPrefixSeparator + spanIndexBaseName + \"archive\", \"foo:\" + config.IndexPrefixSeparator + serviceIndexBaseName + \"archive\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts,\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanWriteAlias:      \"custom-span-write-alias\", ServiceWriteAlias: \"custom-service-write-alias\",\n\t\t\t},\n\t\t\tindices: []string{\"custom-span-write-alias\", \"custom-service-write-alias\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts,\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanWriteAlias:      \"custom-span-write-alias\",\n\t\t\t\tServiceWriteAlias:   \"custom-service-write-alias\",\n\t\t\t\tWriteAliasSuffix:    \"archive\", // Ignored when explicit aliases are used\n\t\t\t},\n\t\t\tindices: []string{\"custom-span-write-alias\", \"custom-service-write-alias\"},\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tClient: clientFn, Logger: logger, MetricsFactory: metricsFactory,\n\t\t\t\tSpanIndex: spanIndexOpts, ServiceIndex: serviceIndexOpts, IndexPrefix: \"foo:\",\n\t\t\t\tUseReadWriteAliases: true,\n\t\t\t\tSpanWriteAlias:      \"production-traces-write\",\n\t\t\t\tServiceWriteAlias:   \"production-services-write\",\n\t\t\t},\n\t\t\tindices: []string{\"production-traces-write\", \"production-services-write\"},\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\tw := NewSpanWriter(testCase.params)\n\t\tspanIndexName, serviceIndexName := w.spanServiceIndex(date)\n\t\tassert.Equal(t, []string{spanIndexName, serviceIndexName}, testCase.indices)\n\t}\n}\n\nfunc TestClientClose(t *testing.T) {\n\twithSpanWriter(func(w *spanWriterTest) {\n\t\tw.client.On(\"Close\").Return(nil)\n\t\tw.writer.Close()\n\t\tw.client.AssertNumberOfCalls(t, \"Close\", 1)\n\t})\n}\n\n// This test behaves as a large test that checks WriteSpan's behavior as a whole.\n// Extra tests for individual functions are below.\nfunc TestSpanWriter_WriteSpan(t *testing.T) {\n\ttestCases := []struct {\n\t\tcaption            string\n\t\tserviceIndexExists bool\n\t\texpectedError      string\n\t\texpectedLogs       []string\n\t}{\n\t\t{\n\t\t\tcaption:            \"span insertion error\",\n\t\t\tserviceIndexExists: false,\n\t\t\texpectedError:      \"\",\n\t\t\texpectedLogs:       []string{\"Wrote span to ES index\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttestCase := tc\n\t\tt.Run(testCase.caption, func(t *testing.T) {\n\t\t\twithSpanWriter(func(w *spanWriterTest) {\n\t\t\t\tdate, err := time.Parse(time.RFC3339, \"1995-04-21T22:08:41+00:00\")\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tspan := &dbmodel.Span{\n\t\t\t\t\tTraceID:       \"testing-traceid\",\n\t\t\t\t\tSpanID:        \"testing-spanid\",\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t\tProcess: dbmodel.Process{\n\t\t\t\t\t\tServiceName: \"service\",\n\t\t\t\t\t},\n\t\t\t\t\tStartTime: model.TimeAsEpochMicroseconds(date),\n\t\t\t\t}\n\n\t\t\t\tspanIndexName := \"jaeger-span-1995-04-21\"\n\t\t\t\tserviceIndexName := \"jaeger-service-1995-04-21\"\n\t\t\t\tserviceHash := \"de3b5a8f1a79989d\"\n\n\t\t\t\tindexService := &mocks.IndexService{}\n\t\t\t\tindexServicePut := &mocks.IndexService{}\n\t\t\t\tindexSpanPut := &mocks.IndexService{}\n\n\t\t\t\tindexService.On(\"Index\", stringMatcher(spanIndexName)).Return(indexService)\n\t\t\t\tindexService.On(\"Index\", stringMatcher(serviceIndexName)).Return(indexService)\n\n\t\t\t\tindexService.On(\"Type\", stringMatcher(serviceType)).Return(indexServicePut)\n\t\t\t\tindexService.On(\"Type\", stringMatcher(spanType)).Return(indexSpanPut)\n\n\t\t\t\tindexServicePut.On(\"Id\", stringMatcher(serviceHash)).Return(indexServicePut)\n\t\t\t\tindexServicePut.On(\"BodyJson\", mock.AnythingOfType(\"dbmodel.Service\")).Return(indexServicePut)\n\t\t\t\tindexServicePut.On(\"Add\")\n\n\t\t\t\tindexSpanPut.On(\"Id\", mock.AnythingOfType(\"string\")).Return(indexSpanPut)\n\t\t\t\tindexSpanPut.On(\"BodyJson\", mock.AnythingOfType(\"**dbmodel.Span\")).Return(indexSpanPut)\n\t\t\t\tindexSpanPut.On(\"Add\")\n\n\t\t\t\tw.client.On(\"Index\").Return(indexService)\n\n\t\t\t\tw.writer.WriteSpan(date, span)\n\n\t\t\t\tif testCase.expectedError == \"\" {\n\t\t\t\t\tindexServicePut.AssertNumberOfCalls(t, \"Add\", 1)\n\t\t\t\t\tindexSpanPut.AssertNumberOfCalls(t, \"Add\", 1)\n\t\t\t\t} else {\n\t\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\t}\n\n\t\t\t\tfor _, expectedLog := range testCase.expectedLogs {\n\t\t\t\t\tassert.Contains(t, w.logBuffer.String(), expectedLog, \"Log must contain %s, but was %s\", expectedLog, w.logBuffer.String())\n\t\t\t\t}\n\t\t\t\tif len(testCase.expectedLogs) == 0 {\n\t\t\t\t\tassert.Empty(t, w.logBuffer.String())\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestSpanIndexName(t *testing.T) {\n\tdate, err := time.Parse(time.RFC3339, \"1995-04-21T22:08:41+00:00\")\n\trequire.NoError(t, err)\n\tspan := &model.Span{\n\t\tStartTime: date,\n\t}\n\tspanIndexName := indexWithDate(spanIndexBaseName, \"2006-01-02\", span.StartTime)\n\tserviceIndexName := indexWithDate(serviceIndexBaseName, \"2006-01-02\", span.StartTime)\n\tassert.Equal(t, \"jaeger-span-1995-04-21\", spanIndexName)\n\tassert.Equal(t, \"jaeger-service-1995-04-21\", serviceIndexName)\n}\n\nfunc TestWriteSpanInternal(t *testing.T) {\n\twithSpanWriter(func(w *spanWriterTest) {\n\t\tindexService := &mocks.IndexService{}\n\n\t\tindexName := \"jaeger-1995-04-21\"\n\t\tindexService.On(\"Index\", stringMatcher(indexName)).Return(indexService)\n\t\tindexService.On(\"Type\", stringMatcher(spanType)).Return(indexService)\n\t\tindexService.On(\"BodyJson\", mock.AnythingOfType(\"**dbmodel.Span\")).Return(indexService)\n\t\tindexService.On(\"Add\")\n\n\t\tw.client.On(\"Index\").Return(indexService)\n\n\t\tjsonSpan := &dbmodel.Span{}\n\n\t\tw.writer.writeSpanToIndex(indexName, jsonSpan)\n\t\tindexService.AssertNumberOfCalls(t, \"Add\", 1)\n\t\tassert.Empty(t, w.logBuffer.String())\n\t})\n}\n\nfunc TestWriteSpanInternalError(t *testing.T) {\n\twithSpanWriter(func(w *spanWriterTest) {\n\t\tindexService := &mocks.IndexService{}\n\n\t\tindexName := \"jaeger-1995-04-21\"\n\t\tindexService.On(\"Index\", stringMatcher(indexName)).Return(indexService)\n\t\tindexService.On(\"Type\", stringMatcher(spanType)).Return(indexService)\n\t\tindexService.On(\"BodyJson\", mock.AnythingOfType(\"**dbmodel.Span\")).Return(indexService)\n\t\tindexService.On(\"Add\")\n\n\t\tw.client.On(\"Index\").Return(indexService)\n\n\t\tjsonSpan := &dbmodel.Span{\n\t\t\tTraceID: dbmodel.TraceID(\"1\"),\n\t\t\tSpanID:  dbmodel.SpanID(\"0\"),\n\t\t}\n\n\t\tw.writer.writeSpanToIndex(indexName, jsonSpan)\n\t\tindexService.AssertNumberOfCalls(t, \"Add\", 1)\n\t})\n}\n\nfunc TestSpanWriterParamsTTL(t *testing.T) {\n\tlogger, _ := testutils.NewLogger()\n\tmetricsFactory := metricstest.NewFactory(0)\n\ttestCases := []struct {\n\t\tserviceTTL       time.Duration\n\t\tname             string\n\t\texpectedAddCalls int\n\t}{\n\t\t{\n\t\t\tserviceTTL:       0,\n\t\t\tname:             \"uses defaults\",\n\t\t\texpectedAddCalls: 1,\n\t\t},\n\t\t{\n\t\t\tserviceTTL:       1 * time.Nanosecond,\n\t\t\tname:             \"uses provided values\",\n\t\t\texpectedAddCalls: 3,\n\t\t},\n\t}\n\n\tfor _, test := range testCases {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tclient := &mocks.Client{}\n\t\t\tparams := SpanWriterParams{\n\t\t\t\tClient:          func() es.Client { return client },\n\t\t\t\tLogger:          logger,\n\t\t\t\tMetricsFactory:  metricsFactory,\n\t\t\t\tServiceCacheTTL: test.serviceTTL,\n\t\t\t}\n\t\t\tw := NewSpanWriter(params)\n\n\t\t\tsvc := dbmodel.Service{\n\t\t\t\tServiceName:   \"foo\",\n\t\t\t\tOperationName: \"bar\",\n\t\t\t}\n\t\t\tserviceHash := hashCode(svc)\n\n\t\t\tserviceIndexName := \"jaeger-service-1995-04-21\"\n\n\t\t\tindexService := &mocks.IndexService{}\n\n\t\t\tindexService.On(\"Index\", stringMatcher(serviceIndexName)).Return(indexService)\n\t\t\tindexService.On(\"Type\", stringMatcher(serviceType)).Return(indexService)\n\t\t\tindexService.On(\"Id\", stringMatcher(serviceHash)).Return(indexService)\n\t\t\tindexService.On(\"BodyJson\", mock.AnythingOfType(\"dbmodel.Service\")).Return(indexService)\n\t\t\tindexService.On(\"Add\")\n\n\t\t\tclient.On(\"Index\").Return(indexService)\n\n\t\t\tjsonSpan := &dbmodel.Span{\n\t\t\t\tProcess:       dbmodel.Process{ServiceName: \"foo\"},\n\t\t\t\tOperationName: \"bar\",\n\t\t\t}\n\n\t\t\tw.writeService(serviceIndexName, jsonSpan)\n\t\t\ttime.Sleep(1 * time.Nanosecond)\n\t\t\tw.writeService(serviceIndexName, jsonSpan)\n\t\t\ttime.Sleep(1 * time.Nanosecond)\n\t\t\tw.writeService(serviceIndexName, jsonSpan)\n\t\t\tindexService.AssertNumberOfCalls(t, \"Add\", test.expectedAddCalls)\n\t\t})\n\t}\n}\n\nfunc TestTagMap(t *testing.T) {\n\ttags := []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:   \"foo\",\n\t\t\tValue: \"foo\",\n\t\t\tType:  dbmodel.StringType,\n\t\t},\n\t\t{\n\t\t\tKey:   \"a\",\n\t\t\tValue: true,\n\t\t\tType:  dbmodel.BoolType,\n\t\t},\n\t\t{\n\t\t\tKey:   \"b.b\",\n\t\t\tValue: int64(1),\n\t\t\tType:  dbmodel.Int64Type,\n\t\t},\n\t}\n\tdbSpan := dbmodel.Span{Tags: tags, Process: dbmodel.Process{Tags: tags}}\n\tconverter := NewSpanWriter(SpanWriterParams{\n\t\tLogger:            zap.NewNop(),\n\t\tMetricsFactory:    metrics.NullFactory,\n\t\tAllTagsAsFields:   false,\n\t\tTagKeysAsFields:   []string{\"a\", \"b.b\", \"b*\"},\n\t\tTagDotReplacement: \":\",\n\t})\n\tconverter.convertNestedTagsToFieldTags(&dbSpan)\n\n\tassert.Len(t, dbSpan.Tags, 1)\n\tassert.Equal(t, \"foo\", dbSpan.Tags[0].Key)\n\tassert.Len(t, dbSpan.Process.Tags, 1)\n\tassert.Equal(t, \"foo\", dbSpan.Process.Tags[0].Key)\n\n\ttagsMap := map[string]any{}\n\ttagsMap[\"a\"] = true\n\ttagsMap[\"b:b\"] = int64(1)\n\tassert.Equal(t, tagsMap, dbSpan.Tag)\n\tassert.Equal(t, tagsMap, dbSpan.Process.Tag)\n}\n\nfunc TestNewSpanTags(t *testing.T) {\n\ttestCases := []struct {\n\t\tparams   SpanWriterParams\n\t\texpected dbmodel.Span\n\t\tname     string\n\t}{\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tAllTagsAsFields:   true,\n\t\t\t\tTagKeysAsFields:   []string{},\n\t\t\t\tTagDotReplacement: \"\",\n\t\t\t},\n\t\t\texpected: dbmodel.Span{\n\t\t\t\tTag: map[string]any{\"foo\": \"bar\"}, Tags: []dbmodel.KeyValue{},\n\t\t\t\tProcess: dbmodel.Process{Tag: map[string]any{\"bar\": \"baz\"}, Tags: []dbmodel.KeyValue{}},\n\t\t\t},\n\t\t\tname: \"allTagsAsFields\",\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tAllTagsAsFields:   false,\n\t\t\t\tTagKeysAsFields:   []string{\"foo\", \"bar\", \"rere\"},\n\t\t\t\tTagDotReplacement: \"\",\n\t\t\t},\n\t\t\texpected: dbmodel.Span{\n\t\t\t\tTag: map[string]any{\"foo\": \"bar\"}, Tags: []dbmodel.KeyValue{},\n\t\t\t\tProcess: dbmodel.Process{Tag: map[string]any{\"bar\": \"baz\"}, Tags: []dbmodel.KeyValue{}},\n\t\t\t},\n\t\t\tname: \"definedTagNames\",\n\t\t},\n\t\t{\n\t\t\tparams: SpanWriterParams{\n\t\t\t\tAllTagsAsFields:   false,\n\t\t\t\tTagKeysAsFields:   []string{},\n\t\t\t\tTagDotReplacement: \"\",\n\t\t\t},\n\t\t\texpected: dbmodel.Span{\n\t\t\t\tTags: []dbmodel.KeyValue{{\n\t\t\t\t\tKey:   \"foo\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: \"bar\",\n\t\t\t\t}},\n\t\t\t\tProcess: dbmodel.Process{Tags: []dbmodel.KeyValue{{\n\t\t\t\t\tKey:   \"bar\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: \"baz\",\n\t\t\t\t}}},\n\t\t\t},\n\t\t\tname: \"noAllTagsAsFields\",\n\t\t},\n\t}\n\tfor _, test := range testCases {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmSpan := &dbmodel.Span{\n\t\t\t\tTags:    []dbmodel.KeyValue{{Key: \"foo\", Value: \"bar\", Type: dbmodel.StringType}},\n\t\t\t\tProcess: dbmodel.Process{Tags: []dbmodel.KeyValue{{Key: \"bar\", Value: \"baz\", Type: dbmodel.StringType}}},\n\t\t\t}\n\t\t\tparams := test.params\n\t\t\tparams.Logger = zap.NewNop()\n\t\t\tparams.MetricsFactory = metrics.NullFactory\n\t\t\twriter := NewSpanWriter(params)\n\t\t\twriter.convertNestedTagsToFieldTags(mSpan)\n\t\t\tassert.Equal(t, test.expected.Tag, mSpan.Tag)\n\t\t\tassert.Equal(t, test.expected.Tags, mSpan.Tags)\n\t\t\tassert.Equal(t, test.expected.Process.Tag, mSpan.Process.Tag)\n\t\t\tassert.Equal(t, test.expected.Process.Tags, mSpan.Process.Tags)\n\t\t})\n\t}\n}\n\n// stringMatcher can match a string argument when it contains a specific substring q\nfunc stringMatcher(q string) any {\n\tmatchFunc := func(s string) bool {\n\t\treturn strings.Contains(s, q)\n\t}\n\treturn mock.MatchedBy(matchFunc)\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/writerv1.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\ntype SpanWriterV1 struct {\n\tspanWriter CoreSpanWriter\n}\n\n// NewSpanWriterV1 returns the SpanWriterV1 for use\nfunc NewSpanWriterV1(p SpanWriterParams) *SpanWriterV1 {\n\treturn &SpanWriterV1{\n\t\tspanWriter: NewSpanWriter(p),\n\t}\n}\n\n// WriteSpan writes a span and its corresponding service:operation in ElasticSearch\nfunc (s *SpanWriterV1) WriteSpan(_ context.Context, span *model.Span) error {\n\tconverter := NewFromDomain()\n\tjsonSpan := converter.FromDomainEmbedProcess(span)\n\ts.spanWriter.WriteSpan(span.StartTime, jsonSpan)\n\treturn nil\n}\n\n// Close closes SpanWriter\nfunc (s *SpanWriterV1) Close() error {\n\treturn s.spanWriter.Close()\n}\n"
  },
  {
    "path": "internal/storage/v1/elasticsearch/spanstore/writerv1_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage spanstore\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore/mocks\"\n)\n\nfunc TestSpanWriterV1_WriteSpan(t *testing.T) {\n\tcoreWriter := &mocks.CoreSpanWriter{}\n\ts := &model.Span{\n\t\tTags:    []model.KeyValue{{Key: \"foo\", VStr: \"bar\"}},\n\t\tProcess: &model.Process{Tags: []model.KeyValue{{Key: \"bar\", VStr: \"baz\"}}},\n\t}\n\twriterV1 := &SpanWriterV1{spanWriter: coreWriter}\n\tconverter := NewFromDomain()\n\tcoreWriter.On(\"WriteSpan\", s.StartTime, converter.FromDomainEmbedProcess(s))\n\terr := writerV1.WriteSpan(context.Background(), s)\n\trequire.NoError(t, err)\n}\n\nfunc TestSpanWriterV1_Close(t *testing.T) {\n\tcoreWriter := &mocks.CoreSpanWriter{}\n\tcoreWriter.On(\"Close\").Return(nil)\n\twriterV1 := &SpanWriterV1{spanWriter: coreWriter}\n\terr := writerV1.Close()\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "internal/storage/v1/factory.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storage\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\n// Purger defines an interface that is capable of purging the storage.\n// Only meant to be used from integration tests.\ntype Purger interface {\n\t// Purge removes all data from the storage.\n\tPurge(context.Context) error\n}\n\n// SamplingStoreFactory defines an interface that is capable of returning the necessary backends for\n// adaptive sampling.\ntype SamplingStoreFactory interface {\n\t// CreateLock creates a distributed lock.\n\tCreateLock() (distributedlock.Lock, error)\n\t// CreateSamplingStore creates a sampling store.\n\tCreateSamplingStore(maxBuckets int) (samplingstore.Store, error)\n}\n\n// MetricStoreFactory defines an interface for a factory that can create implementations of different metrics storage components.\ntype MetricStoreFactory interface {\n\tCreateMetricsReader() (metricstore.Reader, error)\n}\n\n// V1MetricStoreFactory is a v1 version of MetricStoreFactory.\n// Implementations are encouraged to implement storage.Configurable interface.\n//\n// # See also\n//\n// storage.Configurable\ntype V1MetricStoreFactory interface {\n\tMetricStoreFactory\n\t// Initialize performs internal initialization of the factory, such as opening connections to the backend store.\n\t// It is called after all configuration of the factory itself has been done.\n\tInitialize(telset telemetry.Settings) error\n}\n\n// ArchiveCapable is an interface that can be implemented by some storage implementations\n// to indicate that they are capable of archiving data.\ntype ArchiveCapable interface {\n\tIsArchiveCapable() bool\n}\n"
  },
  {
    "path": "internal/storage/v1/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"flag\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/spf13/viper\"\n\tmock \"github.com/stretchr/testify/mock\"\n\t\"go.uber.org/zap\"\n)\n\n// NewConfigurable creates a new instance of Configurable. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewConfigurable(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Configurable {\n\tmock := &Configurable{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Configurable is an autogenerated mock type for the Configurable type\ntype Configurable struct {\n\tmock.Mock\n}\n\ntype Configurable_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Configurable) EXPECT() *Configurable_Expecter {\n\treturn &Configurable_Expecter{mock: &_m.Mock}\n}\n\n// AddFlags provides a mock function for the type Configurable\nfunc (_mock *Configurable) AddFlags(flagSet *flag.FlagSet) {\n\t_mock.Called(flagSet)\n\treturn\n}\n\n// Configurable_AddFlags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddFlags'\ntype Configurable_AddFlags_Call struct {\n\t*mock.Call\n}\n\n// AddFlags is a helper method to define mock.On call\n//   - flagSet *flag.FlagSet\nfunc (_e *Configurable_Expecter) AddFlags(flagSet interface{}) *Configurable_AddFlags_Call {\n\treturn &Configurable_AddFlags_Call{Call: _e.mock.On(\"AddFlags\", flagSet)}\n}\n\nfunc (_c *Configurable_AddFlags_Call) Run(run func(flagSet *flag.FlagSet)) *Configurable_AddFlags_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *flag.FlagSet\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*flag.FlagSet)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Configurable_AddFlags_Call) Return() *Configurable_AddFlags_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *Configurable_AddFlags_Call) RunAndReturn(run func(flagSet *flag.FlagSet)) *Configurable_AddFlags_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// InitFromViper provides a mock function for the type Configurable\nfunc (_mock *Configurable) InitFromViper(v *viper.Viper, logger *zap.Logger) {\n\t_mock.Called(v, logger)\n\treturn\n}\n\n// Configurable_InitFromViper_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InitFromViper'\ntype Configurable_InitFromViper_Call struct {\n\t*mock.Call\n}\n\n// InitFromViper is a helper method to define mock.On call\n//   - v *viper.Viper\n//   - logger *zap.Logger\nfunc (_e *Configurable_Expecter) InitFromViper(v interface{}, logger interface{}) *Configurable_InitFromViper_Call {\n\treturn &Configurable_InitFromViper_Call{Call: _e.mock.On(\"InitFromViper\", v, logger)}\n}\n\nfunc (_c *Configurable_InitFromViper_Call) Run(run func(v *viper.Viper, logger *zap.Logger)) *Configurable_InitFromViper_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 *viper.Viper\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(*viper.Viper)\n\t\t}\n\t\tvar arg1 *zap.Logger\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(*zap.Logger)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Configurable_InitFromViper_Call) Return() *Configurable_InitFromViper_Call {\n\t_c.Call.Return()\n\treturn _c\n}\n\nfunc (_c *Configurable_InitFromViper_Call) RunAndReturn(run func(v *viper.Viper, logger *zap.Logger)) *Configurable_InitFromViper_Call {\n\t_c.Run(run)\n\treturn _c\n}\n\n// NewPurger creates a new instance of Purger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewPurger(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Purger {\n\tmock := &Purger{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Purger is an autogenerated mock type for the Purger type\ntype Purger struct {\n\tmock.Mock\n}\n\ntype Purger_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Purger) EXPECT() *Purger_Expecter {\n\treturn &Purger_Expecter{mock: &_m.Mock}\n}\n\n// Purge provides a mock function for the type Purger\nfunc (_mock *Purger) Purge(context1 context.Context) error {\n\tret := _mock.Called(context1)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Purge\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {\n\t\tr0 = returnFunc(context1)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Purger_Purge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Purge'\ntype Purger_Purge_Call struct {\n\t*mock.Call\n}\n\n// Purge is a helper method to define mock.On call\n//   - context1 context.Context\nfunc (_e *Purger_Expecter) Purge(context1 interface{}) *Purger_Purge_Call {\n\treturn &Purger_Purge_Call{Call: _e.mock.On(\"Purge\", context1)}\n}\n\nfunc (_c *Purger_Purge_Call) Run(run func(context1 context.Context)) *Purger_Purge_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Purger_Purge_Call) Return(err error) *Purger_Purge_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Purger_Purge_Call) RunAndReturn(run func(context1 context.Context) error) *Purger_Purge_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewSamplingStoreFactory creates a new instance of SamplingStoreFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewSamplingStoreFactory(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *SamplingStoreFactory {\n\tmock := &SamplingStoreFactory{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// SamplingStoreFactory is an autogenerated mock type for the SamplingStoreFactory type\ntype SamplingStoreFactory struct {\n\tmock.Mock\n}\n\ntype SamplingStoreFactory_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *SamplingStoreFactory) EXPECT() *SamplingStoreFactory_Expecter {\n\treturn &SamplingStoreFactory_Expecter{mock: &_m.Mock}\n}\n\n// CreateLock provides a mock function for the type SamplingStoreFactory\nfunc (_mock *SamplingStoreFactory) CreateLock() (distributedlock.Lock, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateLock\")\n\t}\n\n\tvar r0 distributedlock.Lock\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (distributedlock.Lock, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() distributedlock.Lock); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(distributedlock.Lock)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SamplingStoreFactory_CreateLock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateLock'\ntype SamplingStoreFactory_CreateLock_Call struct {\n\t*mock.Call\n}\n\n// CreateLock is a helper method to define mock.On call\nfunc (_e *SamplingStoreFactory_Expecter) CreateLock() *SamplingStoreFactory_CreateLock_Call {\n\treturn &SamplingStoreFactory_CreateLock_Call{Call: _e.mock.On(\"CreateLock\")}\n}\n\nfunc (_c *SamplingStoreFactory_CreateLock_Call) Run(run func()) *SamplingStoreFactory_CreateLock_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *SamplingStoreFactory_CreateLock_Call) Return(lock distributedlock.Lock, err error) *SamplingStoreFactory_CreateLock_Call {\n\t_c.Call.Return(lock, err)\n\treturn _c\n}\n\nfunc (_c *SamplingStoreFactory_CreateLock_Call) RunAndReturn(run func() (distributedlock.Lock, error)) *SamplingStoreFactory_CreateLock_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateSamplingStore provides a mock function for the type SamplingStoreFactory\nfunc (_mock *SamplingStoreFactory) CreateSamplingStore(maxBuckets int) (samplingstore.Store, error) {\n\tret := _mock.Called(maxBuckets)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateSamplingStore\")\n\t}\n\n\tvar r0 samplingstore.Store\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(int) (samplingstore.Store, error)); ok {\n\t\treturn returnFunc(maxBuckets)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(int) samplingstore.Store); ok {\n\t\tr0 = returnFunc(maxBuckets)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(samplingstore.Store)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(int) error); ok {\n\t\tr1 = returnFunc(maxBuckets)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// SamplingStoreFactory_CreateSamplingStore_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateSamplingStore'\ntype SamplingStoreFactory_CreateSamplingStore_Call struct {\n\t*mock.Call\n}\n\n// CreateSamplingStore is a helper method to define mock.On call\n//   - maxBuckets int\nfunc (_e *SamplingStoreFactory_Expecter) CreateSamplingStore(maxBuckets interface{}) *SamplingStoreFactory_CreateSamplingStore_Call {\n\treturn &SamplingStoreFactory_CreateSamplingStore_Call{Call: _e.mock.On(\"CreateSamplingStore\", maxBuckets)}\n}\n\nfunc (_c *SamplingStoreFactory_CreateSamplingStore_Call) Run(run func(maxBuckets int)) *SamplingStoreFactory_CreateSamplingStore_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 int\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(int)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *SamplingStoreFactory_CreateSamplingStore_Call) Return(store samplingstore.Store, err error) *SamplingStoreFactory_CreateSamplingStore_Call {\n\t_c.Call.Return(store, err)\n\treturn _c\n}\n\nfunc (_c *SamplingStoreFactory_CreateSamplingStore_Call) RunAndReturn(run func(maxBuckets int) (samplingstore.Store, error)) *SamplingStoreFactory_CreateSamplingStore_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewMetricStoreFactory creates a new instance of MetricStoreFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewMetricStoreFactory(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *MetricStoreFactory {\n\tmock := &MetricStoreFactory{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// MetricStoreFactory is an autogenerated mock type for the MetricStoreFactory type\ntype MetricStoreFactory struct {\n\tmock.Mock\n}\n\ntype MetricStoreFactory_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *MetricStoreFactory) EXPECT() *MetricStoreFactory_Expecter {\n\treturn &MetricStoreFactory_Expecter{mock: &_m.Mock}\n}\n\n// CreateMetricsReader provides a mock function for the type MetricStoreFactory\nfunc (_mock *MetricStoreFactory) CreateMetricsReader() (metricstore.Reader, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateMetricsReader\")\n\t}\n\n\tvar r0 metricstore.Reader\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (metricstore.Reader, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() metricstore.Reader); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metricstore.Reader)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// MetricStoreFactory_CreateMetricsReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateMetricsReader'\ntype MetricStoreFactory_CreateMetricsReader_Call struct {\n\t*mock.Call\n}\n\n// CreateMetricsReader is a helper method to define mock.On call\nfunc (_e *MetricStoreFactory_Expecter) CreateMetricsReader() *MetricStoreFactory_CreateMetricsReader_Call {\n\treturn &MetricStoreFactory_CreateMetricsReader_Call{Call: _e.mock.On(\"CreateMetricsReader\")}\n}\n\nfunc (_c *MetricStoreFactory_CreateMetricsReader_Call) Run(run func()) *MetricStoreFactory_CreateMetricsReader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *MetricStoreFactory_CreateMetricsReader_Call) Return(reader metricstore.Reader, err error) *MetricStoreFactory_CreateMetricsReader_Call {\n\t_c.Call.Return(reader, err)\n\treturn _c\n}\n\nfunc (_c *MetricStoreFactory_CreateMetricsReader_Call) RunAndReturn(run func() (metricstore.Reader, error)) *MetricStoreFactory_CreateMetricsReader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewV1MetricStoreFactory creates a new instance of V1MetricStoreFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewV1MetricStoreFactory(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *V1MetricStoreFactory {\n\tmock := &V1MetricStoreFactory{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// V1MetricStoreFactory is an autogenerated mock type for the V1MetricStoreFactory type\ntype V1MetricStoreFactory struct {\n\tmock.Mock\n}\n\ntype V1MetricStoreFactory_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *V1MetricStoreFactory) EXPECT() *V1MetricStoreFactory_Expecter {\n\treturn &V1MetricStoreFactory_Expecter{mock: &_m.Mock}\n}\n\n// CreateMetricsReader provides a mock function for the type V1MetricStoreFactory\nfunc (_mock *V1MetricStoreFactory) CreateMetricsReader() (metricstore.Reader, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateMetricsReader\")\n\t}\n\n\tvar r0 metricstore.Reader\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (metricstore.Reader, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() metricstore.Reader); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(metricstore.Reader)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// V1MetricStoreFactory_CreateMetricsReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateMetricsReader'\ntype V1MetricStoreFactory_CreateMetricsReader_Call struct {\n\t*mock.Call\n}\n\n// CreateMetricsReader is a helper method to define mock.On call\nfunc (_e *V1MetricStoreFactory_Expecter) CreateMetricsReader() *V1MetricStoreFactory_CreateMetricsReader_Call {\n\treturn &V1MetricStoreFactory_CreateMetricsReader_Call{Call: _e.mock.On(\"CreateMetricsReader\")}\n}\n\nfunc (_c *V1MetricStoreFactory_CreateMetricsReader_Call) Run(run func()) *V1MetricStoreFactory_CreateMetricsReader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *V1MetricStoreFactory_CreateMetricsReader_Call) Return(reader metricstore.Reader, err error) *V1MetricStoreFactory_CreateMetricsReader_Call {\n\t_c.Call.Return(reader, err)\n\treturn _c\n}\n\nfunc (_c *V1MetricStoreFactory_CreateMetricsReader_Call) RunAndReturn(run func() (metricstore.Reader, error)) *V1MetricStoreFactory_CreateMetricsReader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// Initialize provides a mock function for the type V1MetricStoreFactory\nfunc (_mock *V1MetricStoreFactory) Initialize(telset telemetry.Settings) error {\n\tret := _mock.Called(telset)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for Initialize\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(telemetry.Settings) error); ok {\n\t\tr0 = returnFunc(telset)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// V1MetricStoreFactory_Initialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Initialize'\ntype V1MetricStoreFactory_Initialize_Call struct {\n\t*mock.Call\n}\n\n// Initialize is a helper method to define mock.On call\n//   - telset telemetry.Settings\nfunc (_e *V1MetricStoreFactory_Expecter) Initialize(telset interface{}) *V1MetricStoreFactory_Initialize_Call {\n\treturn &V1MetricStoreFactory_Initialize_Call{Call: _e.mock.On(\"Initialize\", telset)}\n}\n\nfunc (_c *V1MetricStoreFactory_Initialize_Call) Run(run func(telset telemetry.Settings)) *V1MetricStoreFactory_Initialize_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 telemetry.Settings\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(telemetry.Settings)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *V1MetricStoreFactory_Initialize_Call) Return(err error) *V1MetricStoreFactory_Initialize_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *V1MetricStoreFactory_Initialize_Call) RunAndReturn(run func(telset telemetry.Settings) error) *V1MetricStoreFactory_Initialize_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewArchiveCapable creates a new instance of ArchiveCapable. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewArchiveCapable(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *ArchiveCapable {\n\tmock := &ArchiveCapable{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// ArchiveCapable is an autogenerated mock type for the ArchiveCapable type\ntype ArchiveCapable struct {\n\tmock.Mock\n}\n\ntype ArchiveCapable_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *ArchiveCapable) EXPECT() *ArchiveCapable_Expecter {\n\treturn &ArchiveCapable_Expecter{mock: &_m.Mock}\n}\n\n// IsArchiveCapable provides a mock function for the type ArchiveCapable\nfunc (_mock *ArchiveCapable) IsArchiveCapable() bool {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for IsArchiveCapable\")\n\t}\n\n\tvar r0 bool\n\tif returnFunc, ok := ret.Get(0).(func() bool); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tr0 = ret.Get(0).(bool)\n\t}\n\treturn r0\n}\n\n// ArchiveCapable_IsArchiveCapable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsArchiveCapable'\ntype ArchiveCapable_IsArchiveCapable_Call struct {\n\t*mock.Call\n}\n\n// IsArchiveCapable is a helper method to define mock.On call\nfunc (_e *ArchiveCapable_Expecter) IsArchiveCapable() *ArchiveCapable_IsArchiveCapable_Call {\n\treturn &ArchiveCapable_IsArchiveCapable_Call{Call: _e.mock.On(\"IsArchiveCapable\")}\n}\n\nfunc (_c *ArchiveCapable_IsArchiveCapable_Call) Run(run func()) *ArchiveCapable_IsArchiveCapable_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *ArchiveCapable_IsArchiveCapable_Call) Return(b bool) *ArchiveCapable_IsArchiveCapable_Call {\n\t_c.Call.Return(b)\n\treturn _c\n}\n\nfunc (_c *ArchiveCapable_IsArchiveCapable_Call) RunAndReturn(run func() bool) *ArchiveCapable_IsArchiveCapable_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v1/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storage\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/depstore/factory.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\ntype Factory interface {\n\tCreateDependencyReader() (Reader, error)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/depstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewFactory creates a new instance of Factory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewFactory(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Factory {\n\tmock := &Factory{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Factory is an autogenerated mock type for the Factory type\ntype Factory struct {\n\tmock.Mock\n}\n\ntype Factory_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Factory) EXPECT() *Factory_Expecter {\n\treturn &Factory_Expecter{mock: &_m.Mock}\n}\n\n// CreateDependencyReader provides a mock function for the type Factory\nfunc (_mock *Factory) CreateDependencyReader() (depstore.Reader, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateDependencyReader\")\n\t}\n\n\tvar r0 depstore.Reader\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (depstore.Reader, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() depstore.Reader); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(depstore.Reader)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Factory_CreateDependencyReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateDependencyReader'\ntype Factory_CreateDependencyReader_Call struct {\n\t*mock.Call\n}\n\n// CreateDependencyReader is a helper method to define mock.On call\nfunc (_e *Factory_Expecter) CreateDependencyReader() *Factory_CreateDependencyReader_Call {\n\treturn &Factory_CreateDependencyReader_Call{Call: _e.mock.On(\"CreateDependencyReader\")}\n}\n\nfunc (_c *Factory_CreateDependencyReader_Call) Run(run func()) *Factory_CreateDependencyReader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Factory_CreateDependencyReader_Call) Return(reader depstore.Reader, err error) *Factory_CreateDependencyReader_Call {\n\t_c.Call.Return(reader, err)\n\treturn _c\n}\n\nfunc (_c *Factory_CreateDependencyReader_Call) RunAndReturn(run func() (depstore.Reader, error)) *Factory_CreateDependencyReader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewReader creates a new instance of Reader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Reader {\n\tmock := &Reader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Reader is an autogenerated mock type for the Reader type\ntype Reader struct {\n\tmock.Mock\n}\n\ntype Reader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Reader) EXPECT() *Reader_Expecter {\n\treturn &Reader_Expecter{mock: &_m.Mock}\n}\n\n// GetDependencies provides a mock function for the type Reader\nfunc (_mock *Reader) GetDependencies(ctx context.Context, query depstore.QueryParameters) ([]model.DependencyLink, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetDependencies\")\n\t}\n\n\tvar r0 []model.DependencyLink\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, depstore.QueryParameters) ([]model.DependencyLink, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, depstore.QueryParameters) []model.DependencyLink); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]model.DependencyLink)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, depstore.QueryParameters) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDependencies'\ntype Reader_GetDependencies_Call struct {\n\t*mock.Call\n}\n\n// GetDependencies is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query depstore.QueryParameters\nfunc (_e *Reader_Expecter) GetDependencies(ctx interface{}, query interface{}) *Reader_GetDependencies_Call {\n\treturn &Reader_GetDependencies_Call{Call: _e.mock.On(\"GetDependencies\", ctx, query)}\n}\n\nfunc (_c *Reader_GetDependencies_Call) Run(run func(ctx context.Context, query depstore.QueryParameters)) *Reader_GetDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 depstore.QueryParameters\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(depstore.QueryParameters)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetDependencies_Call) Return(dependencyLinks []model.DependencyLink, err error) *Reader_GetDependencies_Call {\n\t_c.Call.Return(dependencyLinks, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetDependencies_Call) RunAndReturn(run func(ctx context.Context, query depstore.QueryParameters) ([]model.DependencyLink, error)) *Reader_GetDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewWriter creates a new instance of Writer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewWriter(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Writer {\n\tmock := &Writer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Writer is an autogenerated mock type for the Writer type\ntype Writer struct {\n\tmock.Mock\n}\n\ntype Writer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Writer) EXPECT() *Writer_Expecter {\n\treturn &Writer_Expecter{mock: &_m.Mock}\n}\n\n// WriteDependencies provides a mock function for the type Writer\nfunc (_mock *Writer) WriteDependencies(ts time.Time, dependencies []model.DependencyLink) error {\n\tret := _mock.Called(ts, dependencies)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteDependencies\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(time.Time, []model.DependencyLink) error); ok {\n\t\tr0 = returnFunc(ts, dependencies)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Writer_WriteDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteDependencies'\ntype Writer_WriteDependencies_Call struct {\n\t*mock.Call\n}\n\n// WriteDependencies is a helper method to define mock.On call\n//   - ts time.Time\n//   - dependencies []model.DependencyLink\nfunc (_e *Writer_Expecter) WriteDependencies(ts interface{}, dependencies interface{}) *Writer_WriteDependencies_Call {\n\treturn &Writer_WriteDependencies_Call{Call: _e.mock.On(\"WriteDependencies\", ts, dependencies)}\n}\n\nfunc (_c *Writer_WriteDependencies_Call) Run(run func(ts time.Time, dependencies []model.DependencyLink)) *Writer_WriteDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 time.Time\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(time.Time)\n\t\t}\n\t\tvar arg1 []model.DependencyLink\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].([]model.DependencyLink)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Writer_WriteDependencies_Call) Return(err error) *Writer_WriteDependencies_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Writer_WriteDependencies_Call) RunAndReturn(run func(ts time.Time, dependencies []model.DependencyLink) error) *Writer_WriteDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v2/api/depstore/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/depstore/reader.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// QueryParameters contains the parameters that can be used to query dependencies.\ntype QueryParameters struct {\n\tStartTime time.Time\n\tEndTime   time.Time\n}\n\n// Reader can load service dependencies from storage.\ntype Reader interface {\n\tGetDependencies(ctx context.Context, query QueryParameters) ([]model.DependencyLink, error)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/depstore/writer.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// Writer write the dependencies into the storage\ntype Writer interface {\n\tWriteDependencies(ts time.Time, dependencies []model.DependencyLink) error\n}\n"
  },
  {
    "path": "internal/storage/v2/api/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage storage_v2\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/factory.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\n// Factory defines an interface for a factory that can create implementations of\n// different span storage components.\ntype Factory interface {\n\t// CreateTraceReader creates a spanstore.Reader.\n\tCreateTraceReader() (Reader, error)\n\n\t// CreateTraceWriter creates a spanstore.Writer.\n\tCreateTraceWriter() (Writer, error)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"iter\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tmock \"github.com/stretchr/testify/mock\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\n// NewFactory creates a new instance of Factory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewFactory(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Factory {\n\tmock := &Factory{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Factory is an autogenerated mock type for the Factory type\ntype Factory struct {\n\tmock.Mock\n}\n\ntype Factory_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Factory) EXPECT() *Factory_Expecter {\n\treturn &Factory_Expecter{mock: &_m.Mock}\n}\n\n// CreateTraceReader provides a mock function for the type Factory\nfunc (_mock *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateTraceReader\")\n\t}\n\n\tvar r0 tracestore.Reader\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (tracestore.Reader, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() tracestore.Reader); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(tracestore.Reader)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Factory_CreateTraceReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTraceReader'\ntype Factory_CreateTraceReader_Call struct {\n\t*mock.Call\n}\n\n// CreateTraceReader is a helper method to define mock.On call\nfunc (_e *Factory_Expecter) CreateTraceReader() *Factory_CreateTraceReader_Call {\n\treturn &Factory_CreateTraceReader_Call{Call: _e.mock.On(\"CreateTraceReader\")}\n}\n\nfunc (_c *Factory_CreateTraceReader_Call) Run(run func()) *Factory_CreateTraceReader_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Factory_CreateTraceReader_Call) Return(reader tracestore.Reader, err error) *Factory_CreateTraceReader_Call {\n\t_c.Call.Return(reader, err)\n\treturn _c\n}\n\nfunc (_c *Factory_CreateTraceReader_Call) RunAndReturn(run func() (tracestore.Reader, error)) *Factory_CreateTraceReader_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// CreateTraceWriter provides a mock function for the type Factory\nfunc (_mock *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\tret := _mock.Called()\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateTraceWriter\")\n\t}\n\n\tvar r0 tracestore.Writer\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func() (tracestore.Writer, error)); ok {\n\t\treturn returnFunc()\n\t}\n\tif returnFunc, ok := ret.Get(0).(func() tracestore.Writer); ok {\n\t\tr0 = returnFunc()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(tracestore.Writer)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = returnFunc()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Factory_CreateTraceWriter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTraceWriter'\ntype Factory_CreateTraceWriter_Call struct {\n\t*mock.Call\n}\n\n// CreateTraceWriter is a helper method to define mock.On call\nfunc (_e *Factory_Expecter) CreateTraceWriter() *Factory_CreateTraceWriter_Call {\n\treturn &Factory_CreateTraceWriter_Call{Call: _e.mock.On(\"CreateTraceWriter\")}\n}\n\nfunc (_c *Factory_CreateTraceWriter_Call) Run(run func()) *Factory_CreateTraceWriter_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\trun()\n\t})\n\treturn _c\n}\n\nfunc (_c *Factory_CreateTraceWriter_Call) Return(writer tracestore.Writer, err error) *Factory_CreateTraceWriter_Call {\n\t_c.Call.Return(writer, err)\n\treturn _c\n}\n\nfunc (_c *Factory_CreateTraceWriter_Call) RunAndReturn(run func() (tracestore.Writer, error)) *Factory_CreateTraceWriter_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewReader creates a new instance of Reader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewReader(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Reader {\n\tmock := &Reader{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Reader is an autogenerated mock type for the Reader type\ntype Reader struct {\n\tmock.Mock\n}\n\ntype Reader_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Reader) EXPECT() *Reader_Expecter {\n\treturn &Reader_Expecter{mock: &_m.Mock}\n}\n\n// FindTraceIDs provides a mock function for the type Reader\nfunc (_mock *Reader) FindTraceIDs(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraceIDs\")\n\t}\n\n\tvar r0 iter.Seq2[[]tracestore.FoundTraceID, error]\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error]); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(iter.Seq2[[]tracestore.FoundTraceID, error])\n\t\t}\n\t}\n\treturn r0\n}\n\n// Reader_FindTraceIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraceIDs'\ntype Reader_FindTraceIDs_Call struct {\n\t*mock.Call\n}\n\n// FindTraceIDs is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query tracestore.TraceQueryParams\nfunc (_e *Reader_Expecter) FindTraceIDs(ctx interface{}, query interface{}) *Reader_FindTraceIDs_Call {\n\treturn &Reader_FindTraceIDs_Call{Call: _e.mock.On(\"FindTraceIDs\", ctx, query)}\n}\n\nfunc (_c *Reader_FindTraceIDs_Call) Run(run func(ctx context.Context, query tracestore.TraceQueryParams)) *Reader_FindTraceIDs_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 tracestore.TraceQueryParams\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(tracestore.TraceQueryParams)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraceIDs_Call) Return(seq2 iter.Seq2[[]tracestore.FoundTraceID, error]) *Reader_FindTraceIDs_Call {\n\t_c.Call.Return(seq2)\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraceIDs_Call) RunAndReturn(run func(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error]) *Reader_FindTraceIDs_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// FindTraces provides a mock function for the type Reader\nfunc (_mock *Reader) FindTraces(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error] {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for FindTraces\")\n\t}\n\n\tvar r0 iter.Seq2[[]ptrace.Traces, error]\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error]); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(iter.Seq2[[]ptrace.Traces, error])\n\t\t}\n\t}\n\treturn r0\n}\n\n// Reader_FindTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindTraces'\ntype Reader_FindTraces_Call struct {\n\t*mock.Call\n}\n\n// FindTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query tracestore.TraceQueryParams\nfunc (_e *Reader_Expecter) FindTraces(ctx interface{}, query interface{}) *Reader_FindTraces_Call {\n\treturn &Reader_FindTraces_Call{Call: _e.mock.On(\"FindTraces\", ctx, query)}\n}\n\nfunc (_c *Reader_FindTraces_Call) Run(run func(ctx context.Context, query tracestore.TraceQueryParams)) *Reader_FindTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 tracestore.TraceQueryParams\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(tracestore.TraceQueryParams)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraces_Call) Return(seq2 iter.Seq2[[]ptrace.Traces, error]) *Reader_FindTraces_Call {\n\t_c.Call.Return(seq2)\n\treturn _c\n}\n\nfunc (_c *Reader_FindTraces_Call) RunAndReturn(run func(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error]) *Reader_FindTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetOperations provides a mock function for the type Reader\nfunc (_mock *Reader) GetOperations(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error) {\n\tret := _mock.Called(ctx, query)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetOperations\")\n\t}\n\n\tvar r0 []tracestore.Operation\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, tracestore.OperationQueryParams) ([]tracestore.Operation, error)); ok {\n\t\treturn returnFunc(ctx, query)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, tracestore.OperationQueryParams) []tracestore.Operation); ok {\n\t\tr0 = returnFunc(ctx, query)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]tracestore.Operation)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, tracestore.OperationQueryParams) error); ok {\n\t\tr1 = returnFunc(ctx, query)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetOperations_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOperations'\ntype Reader_GetOperations_Call struct {\n\t*mock.Call\n}\n\n// GetOperations is a helper method to define mock.On call\n//   - ctx context.Context\n//   - query tracestore.OperationQueryParams\nfunc (_e *Reader_Expecter) GetOperations(ctx interface{}, query interface{}) *Reader_GetOperations_Call {\n\treturn &Reader_GetOperations_Call{Call: _e.mock.On(\"GetOperations\", ctx, query)}\n}\n\nfunc (_c *Reader_GetOperations_Call) Run(run func(ctx context.Context, query tracestore.OperationQueryParams)) *Reader_GetOperations_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 tracestore.OperationQueryParams\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(tracestore.OperationQueryParams)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetOperations_Call) Return(operations []tracestore.Operation, err error) *Reader_GetOperations_Call {\n\t_c.Call.Return(operations, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetOperations_Call) RunAndReturn(run func(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error)) *Reader_GetOperations_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetServices provides a mock function for the type Reader\nfunc (_mock *Reader) GetServices(ctx context.Context) ([]string, error) {\n\tret := _mock.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetServices\")\n\t}\n\n\tvar r0 []string\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {\n\t\treturn returnFunc(ctx)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok {\n\t\tr0 = returnFunc(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]string)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = returnFunc(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// Reader_GetServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetServices'\ntype Reader_GetServices_Call struct {\n\t*mock.Call\n}\n\n// GetServices is a helper method to define mock.On call\n//   - ctx context.Context\nfunc (_e *Reader_Expecter) GetServices(ctx interface{}) *Reader_GetServices_Call {\n\treturn &Reader_GetServices_Call{Call: _e.mock.On(\"GetServices\", ctx)}\n}\n\nfunc (_c *Reader_GetServices_Call) Run(run func(ctx context.Context)) *Reader_GetServices_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetServices_Call) Return(strings []string, err error) *Reader_GetServices_Call {\n\t_c.Call.Return(strings, err)\n\treturn _c\n}\n\nfunc (_c *Reader_GetServices_Call) RunAndReturn(run func(ctx context.Context) ([]string, error)) *Reader_GetServices_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetTraces provides a mock function for the type Reader\nfunc (_mock *Reader) GetTraces(ctx context.Context, traceIDs ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error] {\n\tvar tmpRet mock.Arguments\n\tif len(traceIDs) > 0 {\n\t\ttmpRet = _mock.Called(ctx, traceIDs)\n\t} else {\n\t\ttmpRet = _mock.Called(ctx)\n\t}\n\tret := tmpRet\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTraces\")\n\t}\n\n\tvar r0 iter.Seq2[[]ptrace.Traces, error]\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error]); ok {\n\t\tr0 = returnFunc(ctx, traceIDs...)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(iter.Seq2[[]ptrace.Traces, error])\n\t\t}\n\t}\n\treturn r0\n}\n\n// Reader_GetTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTraces'\ntype Reader_GetTraces_Call struct {\n\t*mock.Call\n}\n\n// GetTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - traceIDs ...tracestore.GetTraceParams\nfunc (_e *Reader_Expecter) GetTraces(ctx interface{}, traceIDs ...interface{}) *Reader_GetTraces_Call {\n\treturn &Reader_GetTraces_Call{Call: _e.mock.On(\"GetTraces\",\n\t\tappend([]interface{}{ctx}, traceIDs...)...)}\n}\n\nfunc (_c *Reader_GetTraces_Call) Run(run func(ctx context.Context, traceIDs ...tracestore.GetTraceParams)) *Reader_GetTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 []tracestore.GetTraceParams\n\t\tvar variadicArgs []tracestore.GetTraceParams\n\t\tif len(args) > 1 {\n\t\t\tvariadicArgs = args[1].([]tracestore.GetTraceParams)\n\t\t}\n\t\targ1 = variadicArgs\n\t\trun(\n\t\t\targ0,\n\t\t\targ1...,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Reader_GetTraces_Call) Return(seq2 iter.Seq2[[]ptrace.Traces, error]) *Reader_GetTraces_Call {\n\t_c.Call.Return(seq2)\n\treturn _c\n}\n\nfunc (_c *Reader_GetTraces_Call) RunAndReturn(run func(ctx context.Context, traceIDs ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error]) *Reader_GetTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// NewWriter creates a new instance of Writer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewWriter(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *Writer {\n\tmock := &Writer{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// Writer is an autogenerated mock type for the Writer type\ntype Writer struct {\n\tmock.Mock\n}\n\ntype Writer_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *Writer) EXPECT() *Writer_Expecter {\n\treturn &Writer_Expecter{mock: &_m.Mock}\n}\n\n// WriteTraces provides a mock function for the type Writer\nfunc (_mock *Writer) WriteTraces(ctx context.Context, td ptrace.Traces) error {\n\tret := _mock.Called(ctx, td)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteTraces\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, ptrace.Traces) error); ok {\n\t\tr0 = returnFunc(ctx, td)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// Writer_WriteTraces_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteTraces'\ntype Writer_WriteTraces_Call struct {\n\t*mock.Call\n}\n\n// WriteTraces is a helper method to define mock.On call\n//   - ctx context.Context\n//   - td ptrace.Traces\nfunc (_e *Writer_Expecter) WriteTraces(ctx interface{}, td interface{}) *Writer_WriteTraces_Call {\n\treturn &Writer_WriteTraces_Call{Call: _e.mock.On(\"WriteTraces\", ctx, td)}\n}\n\nfunc (_c *Writer_WriteTraces_Call) Run(run func(ctx context.Context, td ptrace.Traces)) *Writer_WriteTraces_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 ptrace.Traces\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(ptrace.Traces)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *Writer_WriteTraces_Call) Return(err error) *Writer_WriteTraces_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *Writer_WriteTraces_Call) RunAndReturn(run func(ctx context.Context, td ptrace.Traces) error) *Writer_WriteTraces_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/reader.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"iter\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\n// Reader finds and loads traces and other data from storage.\ntype Reader interface {\n\t// GetTraces returns an iterator that retrieves all traces with given IDs.\n\t// The iterator is single-use: once consumed, it cannot be used again.\n\t//\n\t// Chunking requirements:\n\t// - A single ptrace.Traces chunk MUST NOT contain spans from multiple traces.\n\t// - Large traces MAY be split across multiple, *consecutive* ptrace.Traces chunks.\n\t// - Each returned ptrace.Traces object MUST NOT be empty.\n\t//\n\t// Edge cases:\n\t// - If no spans are found for any given trace ID, the ID is ignored.\n\t// - If none of the trace IDs are found in the storage, an empty iterator is returned.\n\t// - If an error is encountered, the iterator returns the error and stops.\n\tGetTraces(ctx context.Context, traceIDs ...GetTraceParams) iter.Seq2[[]ptrace.Traces, error]\n\n\t// GetServices returns all service names known to the backend from spans\n\t// within its retention period.\n\tGetServices(ctx context.Context) ([]string, error)\n\n\t// GetOperations returns all operation names for a given service\n\t// known to the backend from spans within its retention period.\n\tGetOperations(ctx context.Context, query OperationQueryParams) ([]Operation, error)\n\n\t// FindTraces returns an iterator that retrieves traces matching query parameters.\n\t// The iterator is single-use: once consumed, it cannot be used again.\n\t//\n\t// The chunking rules is the same as for GetTraces.\n\t//\n\t// If no matching traces are found, the function returns an empty iterator.\n\t// If an error is encountered, the iterator returns the error and stops.\n\t//\n\t// There's currently an implementation-dependent ambiguity whether all query filters\n\t// (such as multiple tags) must apply to the same span within a trace, or can be satisfied\n\t// by different spans.\n\tFindTraces(ctx context.Context, query TraceQueryParams) iter.Seq2[[]ptrace.Traces, error]\n\n\t// FindTraceIDs returns an iterator that retrieves IDs of traces matching query parameters.\n\t// The iterator is single-use: once consumed, it cannot be used again.\n\t//\n\t// If no matching traces are found, the function returns an empty iterator.\n\t// If an error is encountered, the iterator returns the error and stops.\n\t//\n\t// This function behaves identically to FindTraces, except that it returns only the list\n\t// of matching trace IDs. This is useful in some contexts, such as batch jobs, where a\n\t// large list of trace IDs may be queried first and then the full traces are loaded\n\t// in batches.\n\tFindTraceIDs(ctx context.Context, query TraceQueryParams) iter.Seq2[[]FoundTraceID, error]\n}\n\n// GetTraceParams contains single-trace parameters for a GetTraces request.\n// Some storage backends (e.g. Tempo) perform GetTraces much more efficiently\n// if they know the approximate time range of the trace.\ntype GetTraceParams struct {\n\t// TraceID is the ID of the trace to retrieve. Required.\n\tTraceID pcommon.TraceID\n\t// Start of the time interval to search for trace ID. Optional.\n\tStart time.Time\n\t// End of the time interval to search for trace ID. Optional.\n\tEnd time.Time\n}\n\n// TraceQueryParams contains query parameters to find traces. For a detailed\n// definition of each field in this message, refer to `TraceQueryParameters` in `jaeger.api_v3`\n// (https://github.com/jaegertracing/jaeger-idl/blob/main/proto/api_v3/query_service.proto).\ntype TraceQueryParams struct {\n\tServiceName   string\n\tOperationName string\n\t// Attributes must initialized with pcommon.NewMap() before use.\n\tAttributes   pcommon.Map\n\tStartTimeMin time.Time\n\tStartTimeMax time.Time\n\tDurationMin  time.Duration\n\tDurationMax  time.Duration\n\tSearchDepth  int\n}\n\n// FoundTraceID is a wrapper around trace ID returned from FindTraceIDs\n// with an optional time range that may be used in GetTraces calls.\n//\n// The time range is provided as an optimization hint for some storage backends\n// that can perform more efficient queries when they know the approximate time range.\n// The value should not be used for precise time-based filtering or assumptions.\n// It is meant as a rough boundary and may not be populated in all cases.\ntype FoundTraceID struct {\n\tTraceID pcommon.TraceID\n\tStart   time.Time\n\tEnd     time.Time\n}\n\nfunc (t *TraceQueryParams) ToSpanStoreQueryParameters() *spanstore.TraceQueryParameters {\n\treturn &spanstore.TraceQueryParameters{\n\t\tServiceName:   t.ServiceName,\n\t\tOperationName: t.OperationName,\n\t\tTags:          jptrace.PcommonMapToPlainMap(t.Attributes),\n\t\tStartTimeMin:  t.StartTimeMin,\n\t\tStartTimeMax:  t.StartTimeMax,\n\t\tDurationMin:   t.DurationMin,\n\t\tDurationMax:   t.DurationMax,\n\t\tNumTraces:     t.SearchDepth,\n\t}\n}\n\n// OperationQueryParams contains parameters of query operations, empty spanKind means get operations for all kinds of span.\ntype OperationQueryParams struct {\n\tServiceName string\n\tSpanKind    string\n}\n\n// Operation contains operation name and span kind\ntype Operation struct {\n\tName     string\n\tSpanKind string\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/reader_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n)\n\nfunc TestToSpanStoreQueryParameters(t *testing.T) {\n\tnow := time.Now()\n\tattributes := pcommon.NewMap()\n\tattributes.PutStr(\"tag-a\", \"val-a\")\n\n\tquery := &TraceQueryParams{\n\t\tServiceName:   \"service\",\n\t\tOperationName: \"operation\",\n\t\tAttributes:    attributes,\n\t\tStartTimeMin:  now,\n\t\tStartTimeMax:  now.Add(time.Minute),\n\t\tDurationMin:   time.Minute,\n\t\tDurationMax:   time.Hour,\n\t\tSearchDepth:   10,\n\t}\n\texpected := &spanstore.TraceQueryParameters{\n\t\tServiceName:   \"service\",\n\t\tOperationName: \"operation\",\n\t\tTags:          map[string]string{\"tag-a\": \"val-a\"},\n\t\tStartTimeMin:  now,\n\t\tStartTimeMax:  now.Add(time.Minute),\n\t\tDurationMin:   time.Minute,\n\t\tDurationMax:   time.Hour,\n\t\tNumTraces:     10,\n\t}\n\trequire.Equal(t, expected, query.ToSpanStoreQueryParameters())\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/tracestoremetrics/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestoremetrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/tracestoremetrics/reader_metrics.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestoremetrics\n\nimport (\n\t\"context\"\n\t\"iter\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar _ tracestore.Reader = (*ReadMetricsDecorator)(nil)\n\n// ReadMetricsDecorator wraps a tracestore.Reader and collects metrics around each read operation.\ntype ReadMetricsDecorator struct {\n\ttraceReader          tracestore.Reader\n\tfindTracesMetrics    *queryMetrics\n\tfindTraceIDsMetrics  *queryMetrics\n\tgetTraceMetrics      *queryMetrics\n\tgetServicesMetrics   *queryMetrics\n\tgetOperationsMetrics *queryMetrics\n}\n\ntype queryMetrics struct {\n\tErrors     metrics.Counter `metric:\"requests\" tags:\"result=err\"`\n\tSuccesses  metrics.Counter `metric:\"requests\" tags:\"result=ok\"`\n\tResponses  metrics.Counter `metric:\"responses\"`\n\tErrLatency metrics.Timer   `metric:\"latency\" tags:\"result=err\"`\n\tOKLatency  metrics.Timer   `metric:\"latency\" tags:\"result=ok\"`\n}\n\nfunc (q *queryMetrics) emit(err error, latency time.Duration, responses int) {\n\tif err != nil {\n\t\tq.Errors.Inc(1)\n\t\tq.ErrLatency.Record(latency)\n\t} else {\n\t\tq.Successes.Inc(1)\n\t\tq.OKLatency.Record(latency)\n\t\tq.Responses.Inc(int64(responses))\n\t}\n}\n\n// NewReaderDecorator returns a new ReadMetricsDecorator.\nfunc NewReaderDecorator(traceReader tracestore.Reader, metricsFactory metrics.Factory) *ReadMetricsDecorator {\n\treturn &ReadMetricsDecorator{\n\t\ttraceReader:          traceReader,\n\t\tfindTracesMetrics:    buildQueryMetrics(\"find_traces\", metricsFactory),\n\t\tfindTraceIDsMetrics:  buildQueryMetrics(\"find_trace_ids\", metricsFactory),\n\t\tgetTraceMetrics:      buildQueryMetrics(\"get_trace\", metricsFactory),\n\t\tgetServicesMetrics:   buildQueryMetrics(\"get_services\", metricsFactory),\n\t\tgetOperationsMetrics: buildQueryMetrics(\"get_operations\", metricsFactory),\n\t}\n}\n\nfunc buildQueryMetrics(operation string, metricsFactory metrics.Factory) *queryMetrics {\n\tqMetrics := &queryMetrics{}\n\tscoped := metricsFactory.Namespace(metrics.NSOptions{Name: \"\", Tags: map[string]string{\"operation\": operation}})\n\tmetrics.Init(qMetrics, scoped, nil)\n\treturn qMetrics\n}\n\n// FindTraces implements tracestore.Reader#FindTraces\nfunc (m *ReadMetricsDecorator) FindTraces(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tstart := time.Now()\n\t\tvar err error\n\t\tlength := 0\n\t\tdefer func() {\n\t\t\tm.findTracesMetrics.emit(err, time.Since(start), length)\n\t\t}()\n\t\tfindTracesIter := m.traceReader.FindTraces(ctx, query)\n\t\tfor traces, iterErr := range findTracesIter {\n\t\t\terr = iterErr\n\t\t\tlength += len(traces)\n\t\t\tif !yield(traces, iterErr) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// FindTraceIDs implements tracestore.Reader#FindTraceIDs\nfunc (m *ReadMetricsDecorator) FindTraceIDs(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\treturn func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\tstart := time.Now()\n\t\tvar err error\n\t\tlength := 0\n\t\tdefer func() {\n\t\t\tm.findTraceIDsMetrics.emit(err, time.Since(start), length)\n\t\t}()\n\t\tfindTraceIDsIter := m.traceReader.FindTraceIDs(ctx, query)\n\t\tfor traceIds, iterErr := range findTraceIDsIter {\n\t\t\terr = iterErr\n\t\t\tlength += len(traceIds)\n\t\t\tif !yield(traceIds, iterErr) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// GetTraces implements tracestore.Reader#GetTraces\nfunc (m *ReadMetricsDecorator) GetTraces(ctx context.Context, traceIDs ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tstart := time.Now()\n\t\tvar err error\n\t\tlength := 0\n\t\tdefer func() {\n\t\t\tm.getTraceMetrics.emit(err, time.Since(start), length)\n\t\t}()\n\t\tgetTraceIter := m.traceReader.GetTraces(ctx, traceIDs...)\n\t\tfor traces, iterErr := range getTraceIter {\n\t\t\terr = iterErr\n\t\t\tlength += len(traces)\n\t\t\tif !yield(traces, iterErr) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// GetServices implements tracestore.Reader#GetServices\nfunc (m *ReadMetricsDecorator) GetServices(ctx context.Context) ([]string, error) {\n\tstart := time.Now()\n\tretMe, err := m.traceReader.GetServices(ctx)\n\tm.getServicesMetrics.emit(err, time.Since(start), len(retMe))\n\treturn retMe, err\n}\n\n// GetOperations implements tracestore.Reader#GetOperations\nfunc (m *ReadMetricsDecorator) GetOperations(\n\tctx context.Context,\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\tstart := time.Now()\n\tretMe, err := m.traceReader.GetOperations(ctx, query)\n\tm.getOperationsMetrics.emit(err, time.Since(start), len(retMe))\n\treturn retMe, err\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/tracestoremetrics/reader_metrics_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestoremetrics\n\nimport (\n\t\"context\"\n\t\"iter\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metricstest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/mocks\"\n)\n\nfunc TestSuccessfulUnderlyingCalls(t *testing.T) {\n\tmf := metricstest.NewFactory(0)\n\n\tmockReader := mocks.Reader{}\n\tmrs := NewReaderDecorator(&mockReader, mf)\n\ttraces := []ptrace.Traces{ptrace.NewTraces(), ptrace.NewTraces()}\n\tmockReader.On(\"GetServices\", context.Background()).Return([]string{\"service-x\"}, nil)\n\tmrs.GetServices(context.Background())\n\toperationQuery := tracestore.OperationQueryParams{ServiceName: \"something\"}\n\tmockReader.On(\"GetOperations\", context.Background(), operationQuery).\n\t\tReturn([]tracestore.Operation{{}}, nil)\n\tmrs.GetOperations(context.Background(), operationQuery)\n\tmockReader.On(\"GetTraces\", context.Background(), []tracestore.GetTraceParams{{}}).Return(emptyIter[ptrace.Traces](traces, nil))\n\tcount := 0\n\tfor range mrs.GetTraces(context.Background(), tracestore.GetTraceParams{}) {\n\t\tif count != 0 {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t}\n\tmockReader.On(\"FindTraces\", context.Background(), tracestore.TraceQueryParams{}).\n\t\tReturn(emptyIter[ptrace.Traces](traces, nil))\n\tcount = 0\n\tfor range mrs.FindTraces(context.Background(), tracestore.TraceQueryParams{}) {\n\t\tif count != 0 {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t}\n\tmockReader.On(\"FindTraceIDs\", context.Background(), tracestore.TraceQueryParams{}).\n\t\tReturn(emptyIter[tracestore.FoundTraceID]([]tracestore.FoundTraceID{{TraceID: [16]byte{}}, {TraceID: [16]byte{}}}, nil))\n\tcount = 0\n\tfor range mrs.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{}) {\n\t\tif count != 0 {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t}\n\tcounters, gauges := mf.Snapshot()\n\texpected := map[string]int64{\n\t\t\"requests|operation=get_operations|result=ok\":  1,\n\t\t\"requests|operation=get_operations|result=err\": 0,\n\t\t\"requests|operation=get_trace|result=ok\":       1,\n\t\t\"requests|operation=get_trace|result=err\":      0,\n\t\t\"requests|operation=find_traces|result=ok\":     1,\n\t\t\"requests|operation=find_traces|result=err\":    0,\n\t\t\"requests|operation=find_trace_ids|result=ok\":  1,\n\t\t\"requests|operation=find_trace_ids|result=err\": 0,\n\t\t\"requests|operation=get_services|result=ok\":    1,\n\t\t\"requests|operation=get_services|result=err\":   0,\n\t\t\"responses|operation=get_trace\":                2,\n\t\t\"responses|operation=find_traces\":              2,\n\t\t\"responses|operation=find_trace_ids\":           2,\n\t\t\"responses|operation=get_operations\":           1,\n\t\t\"responses|operation=get_services\":             1,\n\t}\n\n\texistingKeys := []string{\n\t\t\"latency|operation=get_operations|result=ok.P50\",\n\t\t\"latency|operation=find_traces|result=ok.P50\", // this is not exhaustive\n\t}\n\tnonExistentKeys := []string{\n\t\t\"latency|operation=get_operations|result=err.P50\",\n\t}\n\n\tcheckExpectedExistingAndNonExistentCounters(t, counters, expected, gauges, existingKeys, nonExistentKeys)\n}\n\nfunc checkExpectedExistingAndNonExistentCounters(t *testing.T,\n\tactualCounters,\n\texpectedCounters,\n\tactualGauges map[string]int64,\n\texistingKeys,\n\tnonExistentKeys []string,\n) {\n\tfor k, v := range expectedCounters {\n\t\tassert.Equal(t, v, actualCounters[k], k)\n\t}\n\n\tfor _, k := range existingKeys {\n\t\t_, ok := actualGauges[k]\n\t\tassert.True(t, ok, k)\n\t}\n\n\tfor _, k := range nonExistentKeys {\n\t\t_, ok := actualGauges[k]\n\t\tassert.False(t, ok, k)\n\t}\n}\n\nfunc TestFailingUnderlyingCalls(t *testing.T) {\n\tmf := metricstest.NewFactory(0)\n\n\tmockReader := mocks.Reader{}\n\tmrs := NewReaderDecorator(&mockReader, mf)\n\treturningErr := assert.AnError\n\tmockReader.On(\"GetServices\", context.Background()).\n\t\tReturn(nil, returningErr)\n\tmrs.GetServices(context.Background())\n\toperationQuery := tracestore.OperationQueryParams{ServiceName: \"something\"}\n\tmockReader.On(\"GetOperations\", context.Background(), operationQuery).\n\t\tReturn(nil, returningErr)\n\tmrs.GetOperations(context.Background(), operationQuery)\n\tmockReader.On(\"GetTraces\", context.Background(), []tracestore.GetTraceParams{{}}).\n\t\tReturn(emptyIter[ptrace.Traces](nil, returningErr))\n\tfor range mrs.GetTraces(context.Background(), tracestore.GetTraceParams{}) {\n\t\tt.Log(\"GetTraces iteration\")\n\t}\n\tmockReader.On(\"FindTraces\", context.Background(), tracestore.TraceQueryParams{}).\n\t\tReturn(emptyIter[ptrace.Traces](nil, returningErr))\n\tfor range mrs.FindTraces(context.Background(), tracestore.TraceQueryParams{}) {\n\t\tt.Log(\"FindTraces iteration\")\n\t}\n\tmockReader.On(\"FindTraceIDs\", context.Background(), tracestore.TraceQueryParams{}).\n\t\tReturn(emptyIter[tracestore.FoundTraceID](nil, returningErr))\n\tfor range mrs.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{}) {\n\t\tt.Log(\"FindTraceIDs iteration\")\n\t}\n\tcounters, gauges := mf.Snapshot()\n\texpecteds := map[string]int64{\n\t\t\"requests|operation=get_operations|result=ok\":  0,\n\t\t\"requests|operation=get_operations|result=err\": 1,\n\t\t\"requests|operation=get_trace|result=ok\":       0,\n\t\t\"requests|operation=get_trace|result=err\":      1,\n\t\t\"requests|operation=find_traces|result=ok\":     0,\n\t\t\"requests|operation=find_traces|result=err\":    1,\n\t\t\"requests|operation=find_trace_ids|result=ok\":  0,\n\t\t\"requests|operation=find_trace_ids|result=err\": 1,\n\t\t\"requests|operation=get_services|result=ok\":    0,\n\t\t\"requests|operation=get_services|result=err\":   1,\n\t}\n\n\texistingKeys := []string{\n\t\t\"latency|operation=get_operations|result=err.P50\",\n\t}\n\n\tnonExistentKeys := []string{\n\t\t\"latency|operation=get_operations|result=ok.P50\",\n\t\t\"latency|operation=query|result=ok.P50\", // this is not exhaustive\n\t}\n\n\tcheckExpectedExistingAndNonExistentCounters(t, counters, expecteds, gauges, existingKeys, nonExistentKeys)\n}\n\nfunc emptyIter[T any](td []T, err error) iter.Seq2[[]T, error] {\n\treturn func(yield func([]T, error) bool) {\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\tfor _, t := range td {\n\t\t\tif !yield([]T{t}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/api/tracestore/writer.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n)\n\n// Writer writes spans to storage.\ntype Writer interface {\n\t// WriteTraces writes a batch of spans to storage. Idempotent.\n\t// Implementations are not required to support atomic transactions,\n\t// so if any of the spans fail to be written an error is returned.\n\t// Compatible with OTLP Exporter API.\n\tWriteTraces(ctx context.Context, td ptrace.Traces) error\n}\n"
  },
  {
    "path": "internal/storage/v2/badger/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/v1adapter\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\ntype Factory struct {\n\tv1Factory *badger.Factory\n}\n\nfunc NewFactory(\n\tcfg badger.Config,\n\ttelset telemetry.Settings,\n) (*Factory, error) {\n\tv1Factory := badger.NewFactory()\n\tv1Factory.Config = &cfg\n\terr := v1Factory.Initialize(telset.Metrics, telset.Logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf := Factory{v1Factory: v1Factory}\n\treturn &f, nil\n}\n\nfunc (f *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\tv1Writer, _ := f.v1Factory.CreateSpanWriter() // error is always nil\n\treturn v1adapter.NewTraceWriter(v1Writer), nil\n}\n\nfunc (f *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\tv1Reader, _ := f.v1Factory.CreateSpanReader() // error is always nil\n\treturn v1adapter.NewTraceReader(v1Reader), nil\n}\n\nfunc (f *Factory) CreateDependencyReader() (depstore.Reader, error) {\n\tv1Reader, _ := f.v1Factory.CreateDependencyReader() // error is always nil\n\treturn v1adapter.NewDependencyReader(v1Reader), nil\n}\n\nfunc (f *Factory) CreateSamplingStore(maxBuckets int) (samplingstore.Store, error) {\n\treturn f.v1Factory.CreateSamplingStore(maxBuckets)\n}\n\nfunc (f *Factory) CreateLock() (distributedlock.Lock, error) {\n\treturn f.v1Factory.CreateLock()\n}\n\nfunc (f *Factory) Close() error {\n\treturn f.v1Factory.Close()\n}\n\nfunc (f *Factory) Purge(ctx context.Context) error {\n\treturn f.v1Factory.Purge(ctx)\n}\n"
  },
  {
    "path": "internal/storage/v2/badger/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nfunc TestNewFac(t *testing.T) {\n\ttelset := telemetry.NoopSettings()\n\ttelset.Logger = zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller()))\n\tf, err := NewFactory(*badger.DefaultConfig(), telset)\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateTraceReader()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateDependencyReader()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateSamplingStore(5)\n\trequire.NoError(t, err)\n\n\tlock, err := f.CreateLock()\n\trequire.NoError(t, err)\n\tassert.NotNil(t, lock)\n\n\terr = f.Purge(context.Background())\n\trequire.NoError(t, err)\n\n\terr = f.Close()\n\trequire.NoError(t, err)\n}\n\nfunc TestBadgerStorageFactoryWithConfig(t *testing.T) {\n\tt.Parallel()\n\tcfg := badger.Config{}\n\t_, err := NewFactory(cfg, telemetry.NoopSettings())\n\trequire.ErrorContains(t, err, \"Error Creating Dir: \\\"\\\" err: mkdir : no such file or directory\")\n\n\tcfg = badger.Config{\n\t\tEphemeral:             true,\n\t\tMaintenanceInterval:   5,\n\t\tMetricsUpdateInterval: 10,\n\t}\n\tfactory, err := NewFactory(cfg, telemetry.NoopSettings())\n\trequire.NoError(t, err)\n\tfactory.Close()\n}\n"
  },
  {
    "path": "internal/storage/v2/badger/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage badger\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra\"\n\tcspanstore \"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/tracestoremetrics\"\n\tctracestore \"github.com/jaegertracing/jaeger/internal/storage/v2/cassandra/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/v1adapter\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\ntype Factory struct {\n\tmetricsFactory metrics.Factory\n\tlogger         *zap.Logger\n\tv1Factory      *cassandra.Factory\n\ttracer         trace.TracerProvider\n}\n\n// NewFactory creates and initializes the factory\nfunc NewFactory(opts cassandra.Options, telset telemetry.Settings) (*Factory, error) {\n\tf := &Factory{\n\t\tmetricsFactory: telset.Metrics,\n\t\tlogger:         telset.Logger,\n\t\ttracer:         telset.TracerProvider,\n\t}\n\tbaseFactory, err := newFactoryWithConfig(opts, f.metricsFactory, f.logger, f.tracer)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf.v1Factory = baseFactory\n\treturn f, nil\n}\n\nfunc (f *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\tcorereader, err := cspanstore.NewSpanReader(\n\t\tf.v1Factory.GetSession(),\n\t\tf.metricsFactory,\n\t\tf.logger,\n\t\tf.tracer.Tracer(\"cSpanStore.SpanReader\"),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn tracestoremetrics.NewReaderDecorator(\n\t\tctracestore.NewTraceReader(corereader),\n\t\tf.metricsFactory,\n\t), nil\n}\n\nfunc (f *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\twriter, err := f.v1Factory.CreateSpanWriter()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v1adapter.NewTraceWriter(writer), nil\n}\n\nfunc (f *Factory) CreateDependencyReader() (depstore.Reader, error) {\n\treader, err := f.v1Factory.CreateDependencyReader()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v1adapter.NewDependencyReader(reader), nil\n}\n\nfunc (f *Factory) CreateSamplingStore(maxBuckets int) (samplingstore.Store, error) {\n\treturn f.v1Factory.CreateSamplingStore(maxBuckets)\n}\n\nfunc (f *Factory) Close() error {\n\treturn f.v1Factory.Close()\n}\n\nfunc (f *Factory) Purge(ctx context.Context) error {\n\treturn f.v1Factory.Purge(ctx)\n}\n\nfunc (f *Factory) CreateLock() (distributedlock.Lock, error) {\n\treturn f.v1Factory.CreateLock()\n}\n\n// newFactoryWithConfig initializes factory with Config.\nfunc newFactoryWithConfig(\n\topts cassandra.Options,\n\tmetricsFactory metrics.Factory,\n\tlogger *zap.Logger,\n\ttracer trace.TracerProvider,\n) (*cassandra.Factory, error) {\n\tf := cassandra.NewFactory()\n\t// use this to help with testing\n\tb := &withConfigBuilder{\n\t\tf:              f,\n\t\topts:           &opts,\n\t\tmetricsFactory: metricsFactory,\n\t\tlogger:         logger,\n\t\ttracer:         tracer,\n\t\tinitializer:    f.Initialize, // this can be mocked in tests\n\t}\n\treturn b.build()\n}\n\ntype withConfigBuilder struct {\n\tf              *cassandra.Factory\n\topts           *cassandra.Options\n\tmetricsFactory metrics.Factory\n\tlogger         *zap.Logger\n\ttracer         trace.TracerProvider\n\tinitializer    func(metricsFactory metrics.Factory, logger *zap.Logger, tracer trace.TracerProvider) error\n}\n\nfunc (b *withConfigBuilder) build() (*cassandra.Factory, error) {\n\tb.f.ConfigureFromOptions(b.opts)\n\tif err := b.opts.Configuration.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\terr := b.initializer(b.metricsFactory, b.logger, b.tracer)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b.f, nil\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.opentelemetry.io/otel/trace/noop\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zaptest\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/cassandra/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nfunc TestNewFactoryWithConfig(t *testing.T) {\n\tt.Run(\"valid configuration\", func(t *testing.T) {\n\t\topts := &cassandra.Options{\n\t\t\tConfiguration: config.DefaultConfiguration(),\n\t\t}\n\t\tf := cassandra.NewFactory()\n\t\tb := &withConfigBuilder{\n\t\t\tf:              f,\n\t\t\topts:           opts,\n\t\t\tmetricsFactory: metrics.NullFactory,\n\t\t\tlogger:         zap.NewNop(),\n\t\t\tinitializer:    func(_ metrics.Factory, _ *zap.Logger, _ trace.TracerProvider) error { return nil },\n\t\t}\n\t\t_, err := b.build()\n\t\trequire.NoError(t, err)\n\t})\n\tt.Run(\"connection error\", func(t *testing.T) {\n\t\texpErr := errors.New(\"made-up error\")\n\t\topts := &cassandra.Options{\n\t\t\tConfiguration: config.DefaultConfiguration(),\n\t\t}\n\t\tf := cassandra.NewFactory()\n\t\tb := &withConfigBuilder{\n\t\t\tf:              f,\n\t\t\topts:           opts,\n\t\t\tmetricsFactory: metrics.NullFactory,\n\t\t\tlogger:         zap.NewNop(),\n\t\t\tinitializer:    func(_ metrics.Factory, _ *zap.Logger, _ trace.TracerProvider) error { return expErr },\n\t\t}\n\t\t_, err := b.build()\n\t\trequire.ErrorIs(t, err, expErr)\n\t})\n\tt.Run(\"invalid configuration\", func(t *testing.T) {\n\t\tcfg := cassandra.Options{}\n\t\t_, err := NewFactory(cfg, telemetry.NoopSettings())\n\t\trequire.ErrorContains(t, err, \"Servers: non zero value required\")\n\t})\n}\n\nfunc TestNewFactory(t *testing.T) {\n\tv1Factory := cassandra.NewFactory()\n\tv1Factory.Options = cassandra.NewOptions()\n\tvar (\n\t\tsession = &mocks.Session{}\n\t\tquery   = &mocks.Query{}\n\t)\n\tsession.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\tsession.On(\"Close\").Return()\n\tquery.On(\"Exec\").Return(nil)\n\tcassandra.MockSession(v1Factory, session, nil)\n\trequire.NoError(t, v1Factory.Initialize(metrics.NullFactory, zap.NewNop(), noop.NewTracerProvider()))\n\tf := createFactory(t, v1Factory)\n\t_, err := f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateTraceReader()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateDependencyReader()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateLock()\n\trequire.NoError(t, err)\n\n\t_, err = f.CreateSamplingStore(0)\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, f.Close())\n}\n\nfunc TestCreateTraceReaderError(t *testing.T) {\n\tsession := &mocks.Session{}\n\tquery := &mocks.Query{}\n\tsession.On(\"Query\",\n\t\tmock.AnythingOfType(\"string\"),\n\t\tmock.Anything).Return(query)\n\tsession.On(\"Query\",\n\t\tmock.AnythingOfType(\"string\"),\n\t\tmock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(errors.New(\"table does not exist\"))\n\tv1Factory := cassandra.NewFactory()\n\tcassandra.MockSession(v1Factory, session, nil)\n\trequire.NoError(t, v1Factory.Initialize(metrics.NullFactory, zap.NewNop(), noop.NewTracerProvider()))\n\tf := createFactory(t, v1Factory)\n\tr, err := f.CreateTraceReader()\n\trequire.ErrorContains(t, err, \"neither table operation_names_v2 nor operation_names exist\")\n\trequire.Nil(t, r)\n}\n\nfunc TestCreateTraceWriterErr(t *testing.T) {\n\tv1Factory := cassandra.NewFactory()\n\tv1Factory.Options = &cassandra.Options{\n\t\tConfiguration: config.DefaultConfiguration(),\n\t\tIndex: cassandra.IndexConfig{\n\t\t\tTagBlackList: \"a,b,c\",\n\t\t\tTagWhiteList: \"a,b,c\",\n\t\t},\n\t}\n\tvar (\n\t\tsession = &mocks.Session{}\n\t\tquery   = &mocks.Query{}\n\t)\n\tsession.On(\"Query\", mock.AnythingOfType(\"string\"), mock.Anything).Return(query)\n\tquery.On(\"Exec\").Return(nil)\n\tcassandra.MockSession(v1Factory, session, nil)\n\trequire.NoError(t, v1Factory.Initialize(metrics.NullFactory, zap.NewNop(), noop.NewTracerProvider()))\n\tf := createFactory(t, v1Factory)\n\t_, err := f.CreateTraceWriter()\n\trequire.ErrorContains(t, err, \"only one of TagIndexBlacklist and TagIndexWhitelist can be specified\")\n}\n\nfunc createFactory(t *testing.T, v1Factory *cassandra.Factory) *Factory {\n\treturn &Factory{\n\t\tv1Factory:      v1Factory,\n\t\tmetricsFactory: metrics.NullFactory,\n\t\tlogger:         zaptest.NewLogger(t),\n\t\ttracer:         noop.NewTracerProvider(),\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage cassandra\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/fixtures/.gitignore",
    "content": "actual_*"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/fixtures/cas_01.json",
    "content": "{\n  \"TraceID\": \"AAAAAAAAAAEAAAAAAAAAAA==\",\n  \"SpanID\": 2,\n  \"ParentID\": 3,\n  \"OperationName\": \"test-general-conversion\",\n  \"Flags\": 1,\n  \"StartTime\": 1485467191639875,\n  \"Duration\": 5,\n  \"Tags\": [\n    {\n      \"Key\": \"otel.scope.name\",\n      \"ValueType\": \"string\",\n      \"value_string\": \"testing-library\"\n    },\n    {\n      \"Key\": \"otel.scope.version\",\n      \"ValueType\": \"string\",\n      \"value_string\": \"1.1.1\"\n    },\n    {\n      \"Key\": \"peer.service\",\n      \"ValueType\": \"string\",\n      \"value_string\": \"service-y\"\n    },\n    {\n      \"Key\": \"peer.ipv4\",\n      \"ValueType\": \"int64\",\n      \"value_long\": 23456\n    },\n    {\n      \"Key\": \"blob\",\n      \"ValueType\": \"binary\",\n      \"value_binary\": \"AAAwOQ==\"\n    },\n    {\n      \"Key\": \"temperature\",\n      \"ValueType\": \"float64\",\n      \"value_double\": 72.5\n    },\n    {\n      \"Key\": \"error\",\n      \"ValueType\": \"bool\",\n      \"value_bool\": true\n    },\n    {\n      \"Key\": \"otel.status_description\",\n      \"ValueType\": \"string\",\n      \"value_string\": \"random-message\"\n    },\n    {\n      \"Key\": \"w3c.tracestate\",\n      \"ValueType\": \"string\",\n      \"value_string\": \"some-state\"\n    }\n  ],\n  \"Logs\": [\n    {\n      \"Timestamp\": 1485467191639875,\n      \"Fields\": [\n        {\n          \"Key\": \"event\",\n          \"ValueType\": \"string\",\n          \"value_string\": \"testing-event\"\n        },\n        {\n          \"Key\": \"event-x\",\n          \"ValueType\": \"string\",\n          \"value_string\": \"event-y\"\n        }\n      ]\n    },\n    {\n      \"Timestamp\": 1485467191639875,\n      \"Fields\": [\n        {\n          \"Key\": \"x\",\n          \"ValueType\": \"string\",\n          \"value_string\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"Refs\": [\n    {\n      \"RefType\": \"child-of\",\n      \"TraceID\": \"AAAAAAAAAAEAAAAAAAAAAA==\",\n      \"SpanID\": 3\n    },\n    {\n      \"RefType\": \"follows-from\",\n      \"TraceID\": \"AAAAAAAAAAEAAAAAAAAAAA==\",\n      \"SpanID\": 4\n    },\n    {\n      \"RefType\": \"child-of\",\n      \"TraceID\": \"AAAAAAAAAP8AAAAAAAAAAA==\",\n      \"SpanID\": 255\n    }\n  ],\n  \"Process\": {\n    \"ServiceName\": \"service-x\",\n    \"Tags\": [\n      {\n        \"Key\": \"sdk.version\",\n        \"ValueType\": \"string\",\n        \"value_string\": \"1.2.1\"\n      }\n    ]\n  },\n  \"ServiceName\": \"service-x\",\n  \"SpanHash\": 0\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/fixtures/otel_traces_01.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"service-x\"\n            }\n          },\n          {\n            \"key\": \"sdk.version\",\n            \"value\": {\n              \"stringValue\": \"1.2.1\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {\n            \"name\": \"testing-library\",\n            \"version\": \"1.1.1\"\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000010000000000000000\",\n              \"spanId\": \"0000000000000002\",\n              \"parentSpanId\": \"0000000000000003\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2,\n                \"message\": \"random-message\"\n              },\n              \"traceState\": \"some-state\"\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/from_dbmodel.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/jaegerproto_to_traces.go\n\npackage tracestore\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\tidutils \"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nvar errType = errors.New(\"invalid type\")\n\n// FromDBModel converts dbmodel.Span to ptrace.Traces\nfunc FromDBModel(spans []dbmodel.Span) ptrace.Traces {\n\ttraceData := ptrace.NewTraces()\n\tif len(spans) == 0 {\n\t\treturn traceData\n\t}\n\tresourceSpans := traceData.ResourceSpans()\n\tresourceSpans.EnsureCapacity(len(spans))\n\tdbSpansToSpans(spans, resourceSpans)\n\treturn traceData\n}\n\nfunc dbSpansToSpans(dbSpans []dbmodel.Span, resourceSpans ptrace.ResourceSpansSlice) {\n\tfor i := range dbSpans {\n\t\tspan := &dbSpans[i]\n\t\tresourceSpan := resourceSpans.AppendEmpty()\n\t\tdbProcessToResource(span.Process, resourceSpan.Resource())\n\t\tscopeSpans := resourceSpan.ScopeSpans()\n\t\tscopeSpan := scopeSpans.AppendEmpty()\n\t\tdbSpanToScope(span, scopeSpan)\n\t\tdbSpanToSpan(span, scopeSpan.Spans().AppendEmpty())\n\t}\n}\n\nfunc dbProcessToResource(process dbmodel.Process, resource pcommon.Resource) {\n\tserviceName := process.ServiceName\n\ttags := process.Tags\n\tif serviceName == \"\" && tags == nil {\n\t\treturn\n\t}\n\tattrs := resource.Attributes()\n\tif serviceName != \"\" && serviceName != noServiceName {\n\t\tattrs.EnsureCapacity(len(tags) + 1)\n\t\tattrs.PutStr(otelsemconv.ServiceNameKey, serviceName)\n\t} else {\n\t\tattrs.EnsureCapacity(len(tags))\n\t}\n\tdbTagsToAttributes(tags, attrs)\n}\n\nfunc dbSpanToSpan(dbspan *dbmodel.Span, span ptrace.Span) {\n\tspan.SetTraceID(pcommon.TraceID(dbspan.TraceID))\n\t//nolint:gosec // G115 // we only care about bits, not the interpretation as integer, and this conversion is bitwise lossless\n\tspan.SetSpanID(idutils.UInt64ToSpanID(uint64(dbspan.SpanID)))\n\tspan.SetName(dbspan.OperationName)\n\t//nolint:gosec // G115 // dbspan.Flags is guaranteed non-negative by schema constraints\n\tspan.SetFlags(uint32(dbspan.Flags))\n\t//nolint:gosec // G115 // epoch microseconds are semantically non-negative, safe conversion to uint64\n\tspan.SetStartTimestamp(dbTimeStampToOTLPTimeStamp(uint64(dbspan.StartTime)))\n\t//nolint:gosec // G115 // dbspan.StartTime and dbspan.Duration is guaranteed non-negative by schema constraints\n\tspan.SetEndTimestamp(dbTimeStampToOTLPTimeStamp(uint64(dbspan.StartTime + dbspan.Duration)))\n\n\tparentSpanID := dbspan.ParentID\n\tif parentSpanID != 0 {\n\t\t//nolint:gosec // G115 // bit-preserving uint64<->int64 conversion for opaque span ID\n\t\tspan.SetParentSpanID(idutils.UInt64ToSpanID(uint64(parentSpanID)))\n\t}\n\n\tattrs := span.Attributes()\n\tattrs.EnsureCapacity(len(dbspan.Tags))\n\tdbTagsToAttributes(dbspan.Tags, attrs)\n\tif spanKindAttr, ok := attrs.Get(model.SpanKindKey); ok {\n\t\tspan.SetKind(jSpanKindToInternal(spanKindAttr.Str()))\n\t\tattrs.Remove(model.SpanKindKey)\n\t}\n\tsetSpanStatus(attrs, span)\n\n\tspan.TraceState().FromRaw(getTraceStateFromAttrs(attrs))\n\n\t// drop the attributes slice if all of them were replaced during translation\n\tif attrs.Len() == 0 {\n\t\tattrs.Clear()\n\t}\n\n\tdbLogsToSpanEvents(dbspan.Logs, span.Events())\n\tdbReferencesToSpanLinks(dbspan.Refs, parentSpanID, span.Links())\n}\n\nfunc dbTagsToAttributes(tags []dbmodel.KeyValue, attributes pcommon.Map) {\n\tfor _, tag := range tags {\n\t\tswitch tag.ValueType {\n\t\tcase dbmodel.StringType:\n\t\t\tattributes.PutStr(tag.Key, tag.ValueString)\n\t\tcase dbmodel.BoolType:\n\t\t\tattributes.PutBool(tag.Key, tag.ValueBool)\n\t\tcase dbmodel.Int64Type:\n\t\t\tattributes.PutInt(tag.Key, tag.ValueInt64)\n\t\tcase dbmodel.Float64Type:\n\t\t\tattributes.PutDouble(tag.Key, tag.ValueFloat64)\n\t\tcase dbmodel.BinaryType:\n\t\t\tattributes.PutEmptyBytes(tag.Key).FromRaw(tag.ValueBinary)\n\t\tdefault:\n\t\t\tattributes.PutStr(tag.Key, fmt.Sprintf(\"<Unknown Jaeger TagType %q>\", tag.ValueType))\n\t\t}\n\t}\n}\n\nfunc setSpanStatus(attrs pcommon.Map, span ptrace.Span) {\n\tdest := span.Status()\n\tstatusCode := ptrace.StatusCodeUnset\n\tstatusMessage := \"\"\n\tstatusExists := false\n\n\tif errorVal, ok := attrs.Get(tagError); ok && errorVal.Type() == pcommon.ValueTypeBool {\n\t\tif errorVal.Bool() {\n\t\t\tstatusCode = ptrace.StatusCodeError\n\t\t\tattrs.Remove(tagError)\n\t\t\tstatusExists = true\n\n\t\t\tif desc, ok := extractStatusDescFromAttr(attrs); ok {\n\t\t\t\tstatusMessage = desc\n\t\t\t} else if descAttr, ok := attrs.Get(tagHTTPStatusMsg); ok {\n\t\t\t\tstatusMessage = descAttr.Str()\n\t\t\t}\n\t\t}\n\t}\n\n\tif codeAttr, ok := attrs.Get(otelsemconv.OtelStatusCode); ok {\n\t\tif !statusExists {\n\t\t\t// The error tag is the ultimate truth for a Jaeger spans' error\n\t\t\t// status. Only parse the otel.status_code tag if the error tag is\n\t\t\t// not set to true.\n\t\t\tstatusExists = true\n\t\t\tif strings.ToUpper(codeAttr.Str()) == statusOk {\n\t\t\t\tstatusCode = ptrace.StatusCodeOk\n\t\t\t} else if strings.ToUpper(codeAttr.Str()) == statusError {\n\t\t\t\tstatusCode = ptrace.StatusCodeError\n\t\t\t}\n\t\t\tif desc, ok := extractStatusDescFromAttr(attrs); ok {\n\t\t\t\tstatusMessage = desc\n\t\t\t}\n\t\t}\n\t\t// Regardless of error tag value, remove the otel.status_code tag. The\n\t\t// otel.status_message tag will have already been removed if\n\t\t// statusExists is true.\n\t\tattrs.Remove(otelsemconv.OtelStatusCode)\n\t} else if httpCodeAttr, ok := attrs.Get(otelsemconv.HTTPResponseStatusCodeKey); !statusExists && ok {\n\t\t// Fallback to introspecting if this span represents a failed HTTP\n\t\t// request or response, but again, only do so if the `error` tag was\n\t\t// not set to true and no explicit status was sent.\n\t\tif code, err := getStatusCodeFromHTTPStatusAttr(httpCodeAttr, span.Kind()); err == nil {\n\t\t\tif code != ptrace.StatusCodeUnset {\n\t\t\t\tstatusExists = true\n\t\t\t\tstatusCode = code\n\t\t\t}\n\n\t\t\tif msgAttr, ok := attrs.Get(tagHTTPStatusMsg); ok {\n\t\t\t\tstatusMessage = msgAttr.Str()\n\t\t\t}\n\t\t}\n\t}\n\n\tif statusExists {\n\t\tdest.SetCode(statusCode)\n\t\tdest.SetMessage(statusMessage)\n\t}\n}\n\n// extractStatusDescFromAttr returns the OTel status description from attrs\n// along with true if it is set. Otherwise, an empty string and false are\n// returned. The OTel status description attribute is deleted from attrs in\n// the process.\nfunc extractStatusDescFromAttr(attrs pcommon.Map) (string, bool) {\n\tif msgAttr, ok := attrs.Get(otelsemconv.OtelStatusDescription); ok {\n\t\tmsg := msgAttr.Str()\n\t\tattrs.Remove(otelsemconv.OtelStatusDescription)\n\t\treturn msg, true\n\t}\n\treturn \"\", false\n}\n\n// codeFromAttr returns the integer code value from attrVal. An error is\n// returned if the code is not represented by an integer or string value in\n// the attrVal or the value is outside the bounds of an int representation.\nfunc codeFromAttr(attrVal pcommon.Value) (int64, error) {\n\tvar val int64\n\tswitch attrVal.Type() {\n\tcase pcommon.ValueTypeInt:\n\t\tval = attrVal.Int()\n\tcase pcommon.ValueTypeStr:\n\t\tvar err error\n\t\tval, err = strconv.ParseInt(attrVal.Str(), 10, 0)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"%w: %s\", errType, attrVal.Type().String())\n\t}\n\treturn val, nil\n}\n\nfunc getStatusCodeFromHTTPStatusAttr(attrVal pcommon.Value, kind ptrace.SpanKind) (ptrace.StatusCode, error) {\n\tstatusCode, err := codeFromAttr(attrVal)\n\tif err != nil {\n\t\treturn ptrace.StatusCodeUnset, err\n\t}\n\n\t// For HTTP status codes in the 4xx range span status MUST be left unset\n\t// in case of SpanKind.SERVER and MUST be set to Error in case of SpanKind.CLIENT.\n\t// For HTTP status codes in the 5xx range, as well as any other code the client\n\t// failed to interpret, span status MUST be set to Error.\n\tif statusCode >= 400 && statusCode < 500 {\n\t\tswitch kind {\n\t\tcase ptrace.SpanKindClient:\n\t\t\treturn ptrace.StatusCodeError, nil\n\t\tcase ptrace.SpanKindServer:\n\t\t\treturn ptrace.StatusCodeUnset, nil\n\t\t}\n\t}\n\n\treturn statusCodeFromHTTP(statusCode), nil\n}\n\n// StatusCodeFromHTTP takes an HTTP status code and return the appropriate OpenTelemetry status code\n// See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status\nfunc statusCodeFromHTTP(httpStatusCode int64) ptrace.StatusCode {\n\tif httpStatusCode >= 100 && httpStatusCode < 399 {\n\t\treturn ptrace.StatusCodeUnset\n\t}\n\treturn ptrace.StatusCodeError\n}\n\nfunc jSpanKindToInternal(spanKind string) ptrace.SpanKind {\n\tswitch spanKind {\n\tcase \"client\":\n\t\treturn ptrace.SpanKindClient\n\tcase \"server\":\n\t\treturn ptrace.SpanKindServer\n\tcase \"producer\":\n\t\treturn ptrace.SpanKindProducer\n\tcase \"consumer\":\n\t\treturn ptrace.SpanKindConsumer\n\tcase \"internal\":\n\t\treturn ptrace.SpanKindInternal\n\t}\n\treturn ptrace.SpanKindUnspecified\n}\n\nfunc dbLogsToSpanEvents(logs []dbmodel.Log, events ptrace.SpanEventSlice) {\n\tif len(logs) == 0 {\n\t\treturn\n\t}\n\n\tevents.EnsureCapacity(len(logs))\n\n\tfor i, log := range logs {\n\t\tvar event ptrace.SpanEvent\n\t\tif events.Len() > i {\n\t\t\tevent = events.At(i)\n\t\t} else {\n\t\t\tevent = events.AppendEmpty()\n\t\t}\n\t\t//nolint:gosec // G115 // dblog.Timestamp is guaranteed non-negative (epoch microseconds) by schema constraints\n\t\tevent.SetTimestamp(dbTimeStampToOTLPTimeStamp(uint64(log.Timestamp)))\n\t\tif len(log.Fields) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tattrs := event.Attributes()\n\t\tattrs.EnsureCapacity(len(log.Fields))\n\t\tdbTagsToAttributes(log.Fields, attrs)\n\t\tif name, ok := attrs.Get(eventNameAttr); ok {\n\t\t\tevent.SetName(name.Str())\n\t\t\tattrs.Remove(eventNameAttr)\n\t\t}\n\t}\n}\n\n// dbReferencesToSpanLinks sets internal span links based on jaeger span references skipping excludeParentID\nfunc dbReferencesToSpanLinks(refs []dbmodel.SpanRef, excludeParentID int64, spanLinks ptrace.SpanLinkSlice) {\n\tif len(refs) == 0 || len(refs) == 1 && refs[0].SpanID == excludeParentID && refs[0].RefType == dbmodel.ChildOf {\n\t\treturn\n\t}\n\n\tspanLinks.EnsureCapacity(len(refs))\n\tfor _, ref := range refs {\n\t\tif ref.SpanID == excludeParentID && ref.RefType == dbmodel.ChildOf {\n\t\t\tcontinue\n\t\t}\n\n\t\tlink := spanLinks.AppendEmpty()\n\t\tlink.SetTraceID(pcommon.TraceID(ref.TraceID))\n\t\t//nolint:gosec // G115 // bit-preserving uint64<->int64 conversion for opaque IDs\n\t\tlink.SetSpanID(idutils.UInt64ToSpanID(uint64(ref.SpanID)))\n\t\tlink.Attributes().PutStr(otelsemconv.AttributeOpentracingRefType, dbRefTypeToAttribute(ref.RefType))\n\t}\n}\n\nfunc getTraceStateFromAttrs(attrs pcommon.Map) string {\n\ttraceState := \"\"\n\t// TODO Bring this inline with solution for jaegertracing/jaeger-client-java #702 once available\n\tif attr, ok := attrs.Get(tagW3CTraceState); ok {\n\t\ttraceState = attr.Str()\n\t\tattrs.Remove(tagW3CTraceState)\n\t}\n\treturn traceState\n}\n\nfunc dbSpanToScope(span *dbmodel.Span, scopeSpan ptrace.ScopeSpans) {\n\tif libraryName, ok := getAndDeleteTag(span, otelsemconv.AttributeOtelScopeName); ok {\n\t\tscopeSpan.Scope().SetName(libraryName)\n\t\tif libraryVersion, ok := getAndDeleteTag(span, otelsemconv.AttributeOtelScopeVersion); ok {\n\t\t\tscopeSpan.Scope().SetVersion(libraryVersion)\n\t\t}\n\t}\n}\n\nfunc getAndDeleteTag(span *dbmodel.Span, key string) (string, bool) {\n\tfor i, tag := range span.Tags {\n\t\tif tag.Key == key {\n\t\t\tval := tag.ValueString\n\t\t\tspan.Tags = append(span.Tags[:i], span.Tags[i+1:]...)\n\t\t\treturn val, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc dbRefTypeToAttribute(ref string) string {\n\tif ref == dbmodel.ChildOf {\n\t\treturn otelsemconv.AttributeOpentracingRefTypeChildOf\n\t}\n\treturn otelsemconv.AttributeOpentracingRefTypeFollowsFrom\n}\n\n// dbTimeStampToOTLPTimeStamp converts the db timestamp which is in microseconds\n// to nanoseconds which is the OTLP standard.\nfunc dbTimeStampToOTLPTimeStamp(timestamp uint64) pcommon.Timestamp {\n\treturn pcommon.Timestamp(timestamp * 1000)\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/from_dbmodel_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/jaegerproto_to_traces_test.go\n\npackage tracestore\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\n// Use timestamp with microsecond granularity to work well with jaeger thrift translation\nvar testSpanEventTime = time.Date(2020, 2, 11, 20, 26, 13, 123000, time.UTC).UnixMicro()\n\nfunc TestCodeFromAttr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tattr pcommon.Value\n\t\tcode int64\n\t\terr  error\n\t}{\n\t\t{\n\t\t\tname: \"ok-string\",\n\t\t\tattr: pcommon.NewValueStr(\"0\"),\n\t\t\tcode: 0,\n\t\t},\n\n\t\t{\n\t\t\tname: \"ok-int\",\n\t\t\tattr: pcommon.NewValueInt(1),\n\t\t\tcode: 1,\n\t\t},\n\n\t\t{\n\t\t\tname: \"wrong-type\",\n\t\t\tattr: pcommon.NewValueBool(true),\n\t\t\tcode: 0,\n\t\t\terr:  errType,\n\t\t},\n\n\t\t{\n\t\t\tname: \"invalid-string\",\n\t\t\tattr: pcommon.NewValueStr(\"inf\"),\n\t\t\tcode: 0,\n\t\t\terr:  strconv.ErrSyntax,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcode, err := codeFromAttr(test.attr)\n\t\t\tif test.err != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.code, code)\n\t\t})\n\t}\n}\n\nfunc TestZeroBatchLength(t *testing.T) {\n\ttrace := FromDBModel([]dbmodel.Span{})\n\tassert.Equal(t, 0, trace.ResourceSpans().Len())\n}\n\nfunc TestEmptyServiceNameAndTags(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tbatches []dbmodel.Span\n\t}{\n\t\t{\n\t\t\tname: \"empty service with nil tags\",\n\t\t\tbatches: []dbmodel.Span{\n\t\t\t\t{\n\t\t\t\t\tProcess: dbmodel.Process{\n\t\t\t\t\t\tServiceName: \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty service with tags\",\n\t\t\tbatches: []dbmodel.Span{\n\t\t\t\t{\n\t\t\t\t\tProcess: dbmodel.Process{\n\t\t\t\t\t\tServiceName: \"\",\n\t\t\t\t\t\tTags:        []dbmodel.KeyValue{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttrace := FromDBModel(test.batches)\n\t\t\tassert.Equal(t, 1, trace.ResourceSpans().Len())\n\t\t\tassert.Equal(t, 0, trace.ResourceSpans().At(0).Resource().Attributes().Len())\n\t\t})\n\t}\n}\n\nfunc TestEmptySpansAndProcess(t *testing.T) {\n\ttrace := FromDBModel([]dbmodel.Span{{}})\n\tassert.Equal(t, 1, trace.ResourceSpans().Len())\n}\n\nfunc Test_dbSpansToSpans_EmptySpans(t *testing.T) {\n\tspans := []dbmodel.Span{{}}\n\ttraceData := ptrace.NewTraces()\n\trss := traceData.ResourceSpans()\n\tdbSpansToSpans(spans, rss)\n\tassert.Equal(t, 1, rss.Len())\n}\n\nfunc TestGetStatusCodeFromHTTPStatusAttr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tattr pcommon.Value\n\t\tkind ptrace.SpanKind\n\t\tcode ptrace.StatusCode\n\t\terr  string\n\t}{\n\t\t{\n\t\t\tname: \"string-unknown\",\n\t\t\tattr: pcommon.NewValueStr(\"10\"),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\n\t\t{\n\t\t\tname: \"string-ok\",\n\t\t\tattr: pcommon.NewValueStr(\"101\"),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeUnset,\n\t\t},\n\n\t\t{\n\t\t\tname: \"int-not-found\",\n\t\t\tattr: pcommon.NewValueInt(404),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"int-not-found-client-span\",\n\t\t\tattr: pcommon.NewValueInt(404),\n\t\t\tkind: ptrace.SpanKindServer,\n\t\t\tcode: ptrace.StatusCodeUnset,\n\t\t},\n\t\t{\n\t\t\tname: \"int-invalid-arg\",\n\t\t\tattr: pcommon.NewValueInt(408),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"int-internal\",\n\t\t\tattr: pcommon.NewValueInt(500),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong value\",\n\t\t\tattr: pcommon.NewValueBool(true),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeUnset,\n\t\t\terr:  \"invalid type: Bool\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcode, err := getStatusCodeFromHTTPStatusAttr(test.attr, test.kind)\n\t\t\tif test.err != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.code, code)\n\t\t})\n\t}\n}\n\nfunc Test_dbLogsToSpanEvents(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspan := traces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.Events().AppendEmpty().SetName(\"event1\")\n\tspan.Events().AppendEmpty().SetName(\"event2\")\n\tspan.Events().AppendEmpty().Attributes().PutStr(eventNameAttr, \"testing\")\n\tlogs := []dbmodel.Log{\n\t\t{\n\t\t\tTimestamp: testSpanEventTime,\n\t\t},\n\t\t{\n\t\t\tTimestamp: testSpanEventTime,\n\t\t},\n\t}\n\tdbLogsToSpanEvents(logs, span.Events())\n\tfor i := range logs {\n\t\tassert.Equal(t, testSpanEventTime, int64(span.Events().At(i).Timestamp()/1000))\n\t}\n\tassert.Equal(t, 1, span.Events().At(2).Attributes().Len())\n\tassert.Empty(t, span.Events().At(2).Name())\n}\n\nfunc Test_dbTagsToAttributes(t *testing.T) {\n\ttags := []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:       \"bool-val\",\n\t\t\tValueType: dbmodel.BoolType,\n\t\t\tValueBool: true,\n\t\t},\n\t\t{\n\t\t\tKey:        \"int-val\",\n\t\t\tValueType:  dbmodel.Int64Type,\n\t\t\tValueInt64: 123,\n\t\t},\n\t\t{\n\t\t\tKey:         \"string-val\",\n\t\t\tValueType:   dbmodel.StringType,\n\t\t\tValueString: \"abc\",\n\t\t},\n\t\t{\n\t\t\tKey:          \"double-val\",\n\t\t\tValueType:    dbmodel.Float64Type,\n\t\t\tValueFloat64: 1.23,\n\t\t},\n\t\t{\n\t\t\tKey:         \"binary-val\",\n\t\t\tValueType:   dbmodel.BinaryType,\n\t\t\tValueBinary: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7D, 0x98},\n\t\t},\n\t\t{\n\t\t\tKey:       \"testing-key\",\n\t\t\tValueType: \"some random value\",\n\t\t},\n\t}\n\n\texpected := pcommon.NewMap()\n\texpected.PutBool(\"bool-val\", true)\n\texpected.PutInt(\"int-val\", 123)\n\texpected.PutStr(\"string-val\", \"abc\")\n\texpected.PutDouble(\"double-val\", 1.23)\n\texpected.PutEmptyBytes(\"binary-val\").FromRaw([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7D, 0x98})\n\texpected.PutStr(\"testing-key\", \"<Unknown Jaeger TagType \\\"some random value\\\">\")\n\n\tgot := pcommon.NewMap()\n\tdbTagsToAttributes(tags, got)\n\n\trequire.Equal(t, expected, got)\n}\n\nfunc TestSetInternalSpanStatus(t *testing.T) {\n\temptyStatus := ptrace.NewStatus()\n\n\tokStatus := ptrace.NewStatus()\n\tokStatus.SetCode(ptrace.StatusCodeOk)\n\n\terrorStatus := ptrace.NewStatus()\n\terrorStatus.SetCode(ptrace.StatusCodeError)\n\n\terrorStatusWithMessage := ptrace.NewStatus()\n\terrorStatusWithMessage.SetCode(ptrace.StatusCodeError)\n\terrorStatusWithMessage.SetMessage(\"Error: Invalid argument\")\n\n\terrorStatusWith404Message := ptrace.NewStatus()\n\terrorStatusWith404Message.SetCode(ptrace.StatusCodeError)\n\terrorStatusWith404Message.SetMessage(\"HTTP 404: Not Found\")\n\n\ttests := []struct {\n\t\tname             string\n\t\tattrs            map[string]any\n\t\tstatus           ptrace.Status\n\t\tkind             ptrace.SpanKind\n\t\tattrsModifiedLen int // Length of attributes map after dropping converted fields\n\t}{\n\t\t{\n\t\t\tname:             \"No tags set -> OK status\",\n\t\t\tstatus:           emptyStatus,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"error tag set -> Error status\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError: true,\n\t\t\t},\n\t\t\tstatus:           errorStatus,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"status.code is set as string\",\n\t\t\tattrs: map[string]any{\n\t\t\t\totelsemconv.OtelStatusCode: statusOk,\n\t\t\t},\n\t\t\tstatus:           okStatus,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"status.code, status.message and error tags are set\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError:                          true,\n\t\t\t\totelsemconv.OtelStatusCode:        statusError,\n\t\t\t\totelsemconv.OtelStatusDescription: \"Error: Invalid argument\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWithMessage,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"http.status_code tag is set as string\",\n\t\t\tattrs: map[string]any{\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: \"404\",\n\t\t\t},\n\t\t\tstatus:           errorStatus,\n\t\t\tattrsModifiedLen: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"http.status_code, http.status_message and error tags are set\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError:                              true,\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: 404,\n\t\t\t\ttagHTTPStatusMsg:                      \"HTTP 404: Not Found\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWith404Message,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"status.code has precedence over http.status_code.\",\n\t\t\tattrs: map[string]any{\n\t\t\t\totelsemconv.OtelStatusCode:            statusOk,\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: 500,\n\t\t\t\ttagHTTPStatusMsg:                      \"Server Error\",\n\t\t\t},\n\t\t\tstatus:           okStatus,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"Ignore http.status_code == 200 if error set to true.\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError:                              true,\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: http.StatusOK,\n\t\t\t},\n\t\t\tstatus:           errorStatus,\n\t\t\tattrsModifiedLen: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"status.error has precedence over http.status_error.\",\n\t\t\tattrs: map[string]any{\n\t\t\t\totelsemconv.OtelStatusCode:            statusError,\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: 500,\n\t\t\t\ttagHTTPStatusMsg:                      \"Server Error\",\n\t\t\t},\n\t\t\tstatus:           errorStatus,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"the 4xx range span status MUST be left unset in case of SpanKind.SERVER\",\n\t\t\tkind: ptrace.SpanKindServer,\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError:                              false,\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: 404,\n\t\t\t},\n\t\t\tstatus:           emptyStatus,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"whether tagHttpStatusMsg is set as string\",\n\t\t\tattrs: map[string]any{\n\t\t\t\totelsemconv.HTTPResponseStatusCodeKey: 404,\n\t\t\t\ttagHTTPStatusMsg:                      \"HTTP 404: Not Found\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWith404Message,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tspan := ptrace.NewSpan()\n\t\t\tspan.SetKind(test.kind)\n\t\t\tstatus := span.Status()\n\t\t\tattrs := pcommon.NewMap()\n\t\t\trequire.NoError(t, attrs.FromRaw(test.attrs))\n\t\t\tsetSpanStatus(attrs, span)\n\t\t\tassert.Equal(t, test.status, status)\n\t\t\tassert.Equal(t, test.attrsModifiedLen, attrs.Len())\n\t\t})\n\t}\n}\n\nfunc TestJSpanKindToInternal(t *testing.T) {\n\ttests := []struct {\n\t\tjSpanKind    string\n\t\totlpSpanKind ptrace.SpanKind\n\t}{\n\t\t{\n\t\t\tjSpanKind:    \"client\",\n\t\t\totlpSpanKind: ptrace.SpanKindClient,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"server\",\n\t\t\totlpSpanKind: ptrace.SpanKindServer,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"producer\",\n\t\t\totlpSpanKind: ptrace.SpanKindProducer,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"consumer\",\n\t\t\totlpSpanKind: ptrace.SpanKindConsumer,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"internal\",\n\t\t\totlpSpanKind: ptrace.SpanKindInternal,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"all-others\",\n\t\t\totlpSpanKind: ptrace.SpanKindUnspecified,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.jSpanKind, func(t *testing.T) {\n\t\t\tassert.Equal(t, test.otlpSpanKind, jSpanKindToInternal(test.jSpanKind))\n\t\t})\n\t}\n}\n\nfunc BenchmarkProtoBatchToInternalTraces(b *testing.B) {\n\tdata, err := os.ReadFile(\"fixtures/cas_01.json\")\n\trequire.NoError(b, err)\n\tvar batch dbmodel.Span\n\terr = json.Unmarshal(data, &batch)\n\trequire.NoError(b, err)\n\n\tb.ResetTimer()\n\tfor n := 0; n < b.N; n++ {\n\t\tFromDBModel([]dbmodel.Span{batch})\n\t}\n}\n\nfunc TestFromDbModel_Fixtures(t *testing.T) {\n\ttracesStr, batchStr := loadFixtures(t, 1)\n\tvar batch dbmodel.Span\n\terr := json.Unmarshal(batchStr, &batch)\n\trequire.NoError(t, err)\n\ttd := FromDBModel([]dbmodel.Span{batch})\n\ttestTraces(t, tracesStr, td)\n\tbatches := ToDBModel(td)\n\tassert.Len(t, batches, 1)\n\ttestSpans(t, batchStr, batches[0])\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/reader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"iter\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/v1adapter\"\n)\n\ntype TraceReader struct {\n\treader   spanstore.CoreSpanReader\n\tfallback tracestore.Reader\n}\n\nfunc NewTraceReader(reader spanstore.CoreSpanReader) *TraceReader {\n\treturn &TraceReader{\n\t\treader:   reader,\n\t\tfallback: v1adapter.NewTraceReader(reader),\n\t}\n}\n\nfunc (r *TraceReader) GetServices(ctx context.Context) ([]string, error) {\n\treturn r.reader.GetServices(ctx)\n}\n\nfunc (r *TraceReader) GetOperations(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error) {\n\treturn r.reader.GetOperationsV2(ctx, query)\n}\n\nfunc (r *TraceReader) GetTraces(ctx context.Context, traceIDs ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error] {\n\treturn r.fallback.GetTraces(ctx, traceIDs...)\n}\n\nfunc (r *TraceReader) FindTraces(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error] {\n\treturn r.fallback.FindTraces(ctx, query)\n}\n\nfunc (r *TraceReader) FindTraceIDs(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\treturn r.fallback.FindTraceIDs(ctx, query)\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/reader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nfunc TestNewTraceReader(t *testing.T) {\n\treader := NewTraceReader(&mocks.CoreSpanReader{})\n\tassert.NotNil(t, reader)\n\ttraceids := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{})\n\tassert.NotNil(t, traceids)\n\ttrace := reader.GetTraces(context.Background(), tracestore.GetTraceParams{})\n\tassert.NotNil(t, trace)\n\ttraces := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{})\n\tassert.NotNil(t, traces)\n}\n\nfunc TestGetServices(t *testing.T) {\n\tservices := []string{\"service-1\", \"service-2\"}\n\treader := mocks.CoreSpanReader{}\n\treader.On(\"GetServices\", mock.Anything).Return(services, nil)\n\ttracereader := &TraceReader{reader: &reader}\n\tgot, err := tracereader.GetServices(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, services, got)\n}\n\nfunc TestGetOperationsErr(t *testing.T) {\n\treader := mocks.CoreSpanReader{}\n\treader.On(\"GetOperationsV2\", mock.Anything, mock.Anything).Return(nil, errors.New(\"error\"))\n\ttracereader := &TraceReader{reader: &reader}\n\tgot, err := tracereader.GetOperations(context.Background(), tracestore.OperationQueryParams{\n\t\tServiceName: \"service-1\",\n\t\tSpanKind:    \"some kind\",\n\t})\n\trequire.ErrorContains(t, err, \"error\")\n\trequire.Nil(t, got)\n}\n\nfunc TestGetOperations(t *testing.T) {\n\treader := mocks.CoreSpanReader{}\n\texpected := []tracestore.Operation{\n\t\t{\n\t\t\tName:     \"operation-1\",\n\t\t\tSpanKind: \"some kind\",\n\t\t},\n\t\t{\n\t\t\tName:     \"operation-2\",\n\t\t\tSpanKind: \"some kind\",\n\t\t},\n\t}\n\treader.On(\"GetOperationsV2\", mock.Anything, mock.Anything).Return(expected, nil)\n\ttracereader := &TraceReader{reader: &reader}\n\tgot, err := tracereader.GetOperations(context.Background(), tracestore.OperationQueryParams{\n\t\tServiceName: \"service-1\",\n\t\tSpanKind:    \"some kind\",\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, expected, got)\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/to_dbmodel.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/traces_to_jaegerproto.go\n\npackage tracestore\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nconst (\n\tnoServiceName    = \"OTLPResourceNoServiceName\"\n\teventNameAttr    = \"event\"\n\tstatusError      = \"ERROR\"\n\tstatusOk         = \"OK\"\n\ttagError         = \"error\"\n\ttagW3CTraceState = \"w3c.tracestate\"\n\ttagHTTPStatusMsg = \"http.status_message\"\n)\n\n// ToDBModel translates internal trace data into the DB Spans.\n// Returns a slice of translated DB Spans.\nfunc ToDBModel(td ptrace.Traces) []dbmodel.Span {\n\tresourceSpans := td.ResourceSpans()\n\n\tif resourceSpans.Len() == 0 {\n\t\treturn []dbmodel.Span{}\n\t}\n\n\tbatches := make([]dbmodel.Span, 0, td.SpanCount())\n\tfor i := 0; i < resourceSpans.Len(); i++ {\n\t\trs := resourceSpans.At(i)\n\t\tbatch := resourceSpansToDbSpans(rs)\n\t\tbatches = append(batches, batch...)\n\t}\n\n\treturn batches\n}\n\nfunc resourceSpansToDbSpans(resourceSpans ptrace.ResourceSpans) []dbmodel.Span {\n\tresource := resourceSpans.Resource()\n\tscopeSpans := resourceSpans.ScopeSpans()\n\n\tif scopeSpans.Len() == 0 {\n\t\treturn []dbmodel.Span{}\n\t}\n\n\tprocess := resourceToDbProcess(resource)\n\n\t// Approximate the number of the spans as the number of the spans in the first\n\t// instrumentation library info.\n\tdbSpans := make([]dbmodel.Span, 0, scopeSpans.At(0).Spans().Len())\n\n\tfor _, scopeSpan := range scopeSpans.All() {\n\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\tdbSpan := spanToDbSpan(span, scopeSpan.Scope(), process)\n\t\t\tdbSpans = append(dbSpans, dbSpan)\n\t\t}\n\t}\n\n\treturn dbSpans\n}\n\nfunc resourceToDbProcess(resource pcommon.Resource) dbmodel.Process {\n\tprocess := dbmodel.Process{}\n\tattrs := resource.Attributes()\n\tprocess.ServiceName = noServiceName\n\tif attrs.Len() == 0 {\n\t\treturn process\n\t}\n\ttags := make([]dbmodel.KeyValue, 0, attrs.Len())\n\tfor key, attr := range attrs.All() {\n\t\tif key == otelsemconv.ServiceNameKey {\n\t\t\tprocess.ServiceName = attr.AsString()\n\t\t\tcontinue\n\t\t}\n\t\ttags = append(tags, attributeToDbTag(key, attr))\n\t}\n\tprocess.Tags = tags\n\treturn process\n}\n\nfunc appendTagsFromAttributes(tags []dbmodel.KeyValue, attrs pcommon.Map) []dbmodel.KeyValue {\n\tif attrs.Len() == 0 {\n\t\treturn tags\n\t}\n\tfor key, attr := range attrs.All() {\n\t\ttags = append(tags, attributeToDbTag(key, attr))\n\t}\n\treturn tags\n}\n\nfunc attributeToDbTag(key string, attr pcommon.Value) dbmodel.KeyValue {\n\ttag := dbmodel.KeyValue{Key: key}\n\tswitch attr.Type() {\n\tcase pcommon.ValueTypeInt:\n\t\ttag.ValueType = dbmodel.Int64Type\n\t\ttag.ValueInt64 = attr.Int()\n\tcase pcommon.ValueTypeBool:\n\t\ttag.ValueType = dbmodel.BoolType\n\t\ttag.ValueBool = attr.Bool()\n\tcase pcommon.ValueTypeDouble:\n\t\ttag.ValueType = dbmodel.Float64Type\n\t\ttag.ValueFloat64 = attr.Double()\n\tcase pcommon.ValueTypeBytes:\n\t\ttag.ValueType = dbmodel.BinaryType\n\t\ttag.ValueBinary = attr.Bytes().AsRaw()\n\tcase pcommon.ValueTypeMap, pcommon.ValueTypeSlice:\n\t\ttag.ValueType = dbmodel.StringType\n\t\ttag.ValueString = attr.AsString()\n\tdefault:\n\t\ttag.ValueType = dbmodel.StringType\n\t\ttag.ValueString = attr.Str()\n\t}\n\treturn tag\n}\n\nfunc spanToDbSpan(span ptrace.Span, scope pcommon.InstrumentationScope, process dbmodel.Process) dbmodel.Span {\n\tdbTraceId := dbmodel.TraceID(span.TraceID())\n\tdbReferences := linksToDbSpanRefs(span.Links(), spanIDToDbSpanId(span.ParentSpanID()), dbTraceId)\n\tstartTime := span.StartTimestamp().AsTime()\n\treturn dbmodel.Span{\n\t\tTraceID:       dbTraceId,\n\t\tSpanID:        spanIDToDbSpanId(span.SpanID()),\n\t\tOperationName: span.Name(),\n\t\tRefs:          dbReferences,\n\t\t//nolint:gosec // G115 // OTLP timestamp is nanoseconds since epoch (semantically non-negative), safe to convert to int64 microseconds\n\t\tStartTime: int64(model.TimeAsEpochMicroseconds(startTime)),\n\t\t//nolint:gosec // G115 // span.EndTime - span.StartTime is guaranteed non-negative by schema constraints\n\t\tDuration: int64(model.DurationAsMicroseconds(span.EndTimestamp().AsTime().Sub(startTime))),\n\t\tTags:     getDbTags(span, scope),\n\t\tLogs:     spanEventsToDbLogs(span.Events()),\n\t\tProcess:  process,\n\t\t//nolint:gosec // G115 // span.Flags is uint32, converting to int32 for DB storage (semantically non-negative, fits in int32)\n\t\tFlags:       int32(span.Flags()),\n\t\tServiceName: process.ServiceName,\n\t\tParentID:    spanIDToDbSpanId(span.ParentSpanID()),\n\t}\n}\n\nfunc getDbTags(span ptrace.Span, scope pcommon.InstrumentationScope) []dbmodel.KeyValue {\n\tvar spanKindTag, statusCodeTag, statusMsgTag dbmodel.KeyValue\n\tvar spanKindTagFound, statusCodeTagFound, statusMsgTagFound bool\n\n\tlibraryTags, libraryTagsFound := getTagsFromInstrumentationLibrary(scope)\n\n\ttagsCount := span.Attributes().Len() + len(libraryTags)\n\n\tspanKindTag, spanKindTagFound = getTagFromSpanKind(span.Kind())\n\tif spanKindTagFound {\n\t\ttagsCount++\n\t}\n\tstatus := span.Status()\n\tstatusCodeTag, statusCodeTagFound = getTagFromStatusCode(status.Code())\n\tif statusCodeTagFound {\n\t\ttagsCount++\n\t}\n\n\tstatusMsgTag, statusMsgTagFound = getTagFromStatusMsg(status.Message())\n\tif statusMsgTagFound {\n\t\ttagsCount++\n\t}\n\n\ttraceStateTags, traceStateTagsFound := getTagsFromTraceState(span.TraceState().AsRaw())\n\tif traceStateTagsFound {\n\t\ttagsCount += len(traceStateTags)\n\t}\n\n\tif tagsCount == 0 {\n\t\treturn nil\n\t}\n\n\ttags := make([]dbmodel.KeyValue, 0, tagsCount)\n\tif libraryTagsFound {\n\t\ttags = append(tags, libraryTags...)\n\t}\n\ttags = appendTagsFromAttributes(tags, span.Attributes())\n\tif spanKindTagFound {\n\t\ttags = append(tags, spanKindTag)\n\t}\n\tif statusCodeTagFound {\n\t\ttags = append(tags, statusCodeTag)\n\t}\n\tif statusMsgTagFound {\n\t\ttags = append(tags, statusMsgTag)\n\t}\n\tif traceStateTagsFound {\n\t\ttags = append(tags, traceStateTags...)\n\t}\n\treturn tags\n}\n\nfunc spanIDToDbSpanId(spanID pcommon.SpanID) int64 {\n\t//nolint:gosec // G115 // bit-preserving conversion between uint64 and int64 for opaque SpanID\n\treturn int64(binary.BigEndian.Uint64(spanID[:]))\n}\n\n// linksToDbSpanRefs constructs jaeger span references based on parent span ID and span links.\n// The parent span ID is used to add a CHILD_OF reference, _unless_ it is referenced from one of the links.\nfunc linksToDbSpanRefs(links ptrace.SpanLinkSlice, parentSpanID int64, traceID dbmodel.TraceID) []dbmodel.SpanRef {\n\trefsCount := links.Len()\n\tif parentSpanID != 0 {\n\t\trefsCount++\n\t}\n\n\tif refsCount == 0 {\n\t\treturn nil\n\t}\n\n\trefs := make([]dbmodel.SpanRef, 0, refsCount)\n\n\t// Put parent span ID at the first place because usually backends look for it\n\t// as the first CHILD_OF item in the model.SpanRef slice.\n\tif parentSpanID != 0 {\n\t\trefs = append(refs, dbmodel.SpanRef{\n\t\t\tTraceID: traceID,\n\t\t\tSpanID:  parentSpanID,\n\t\t\tRefType: dbmodel.ChildOf,\n\t\t})\n\t}\n\n\tfor i := 0; i < links.Len(); i++ {\n\t\tlink := links.At(i)\n\t\tlinkTraceID := dbmodel.TraceID(link.TraceID())\n\t\tlinkSpanID := spanIDToDbSpanId(link.SpanID())\n\t\tlinkRefType := dbRefTypeFromLink(link)\n\t\tif parentSpanID != 0 && bytes.Equal(linkTraceID[:], traceID[:]) && linkSpanID == parentSpanID {\n\t\t\t// We already added a reference to this span, but maybe with the wrong type, so override.\n\t\t\trefs[0].RefType = linkRefType\n\t\t\tcontinue\n\t\t}\n\t\trefs = append(refs, dbmodel.SpanRef{\n\t\t\tTraceID: linkTraceID,\n\t\t\tSpanID:  linkSpanID,\n\t\t\tRefType: linkRefType,\n\t\t})\n\t}\n\n\treturn refs\n}\n\nfunc spanEventsToDbLogs(events ptrace.SpanEventSlice) []dbmodel.Log {\n\tif events.Len() == 0 {\n\t\treturn nil\n\t}\n\n\tlogs := make([]dbmodel.Log, 0, events.Len())\n\tfor i := 0; i < events.Len(); i++ {\n\t\tevent := events.At(i)\n\t\tfields := make([]dbmodel.KeyValue, 0, event.Attributes().Len()+1)\n\t\t_, eventAttrFound := event.Attributes().Get(eventNameAttr)\n\t\tif event.Name() != \"\" && !eventAttrFound {\n\t\t\tfields = append(fields, dbmodel.KeyValue{\n\t\t\t\tKey:         eventNameAttr,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: event.Name(),\n\t\t\t})\n\t\t}\n\t\tfields = appendTagsFromAttributes(fields, event.Attributes())\n\t\tlogs = append(logs, dbmodel.Log{\n\t\t\t//nolint:gosec // G115 // Timestamp is guaranteed non-negative by schema constraints\n\t\t\tTimestamp: int64(model.TimeAsEpochMicroseconds(event.Timestamp().AsTime())),\n\t\t\tFields:    fields,\n\t\t})\n\t}\n\n\treturn logs\n}\n\nfunc getTagFromSpanKind(spanKind ptrace.SpanKind) (dbmodel.KeyValue, bool) {\n\tvar tagStr string\n\tswitch spanKind {\n\tcase ptrace.SpanKindClient:\n\t\ttagStr = string(model.SpanKindClient)\n\tcase ptrace.SpanKindServer:\n\t\ttagStr = string(model.SpanKindServer)\n\tcase ptrace.SpanKindProducer:\n\t\ttagStr = string(model.SpanKindProducer)\n\tcase ptrace.SpanKindConsumer:\n\t\ttagStr = string(model.SpanKindConsumer)\n\tcase ptrace.SpanKindInternal:\n\t\ttagStr = string(model.SpanKindInternal)\n\tdefault:\n\t\treturn dbmodel.KeyValue{}, false\n\t}\n\n\treturn dbmodel.KeyValue{\n\t\tKey:         model.SpanKindKey,\n\t\tValueType:   dbmodel.StringType,\n\t\tValueString: tagStr,\n\t}, true\n}\n\nfunc getTagFromStatusCode(statusCode ptrace.StatusCode) (dbmodel.KeyValue, bool) {\n\tswitch statusCode {\n\t// For backward compatibility, we also include the error tag\n\t// which was previously used in the test fixtures\n\tcase ptrace.StatusCodeError:\n\t\treturn dbmodel.KeyValue{\n\t\t\tKey:       tagError,\n\t\t\tValueType: dbmodel.BoolType,\n\t\t\tValueBool: true,\n\t\t}, true\n\tcase ptrace.StatusCodeOk:\n\t\treturn dbmodel.KeyValue{\n\t\t\tKey:         otelsemconv.OtelStatusCode,\n\t\t\tValueType:   dbmodel.StringType,\n\t\t\tValueString: statusOk,\n\t\t}, true\n\t}\n\treturn dbmodel.KeyValue{}, false\n}\n\nfunc getTagFromStatusMsg(statusMsg string) (dbmodel.KeyValue, bool) {\n\tif statusMsg == \"\" {\n\t\treturn dbmodel.KeyValue{}, false\n\t}\n\treturn dbmodel.KeyValue{\n\t\tKey:         otelsemconv.OtelStatusDescription,\n\t\tValueString: statusMsg,\n\t\tValueType:   dbmodel.StringType,\n\t}, true\n}\n\nfunc getTagsFromTraceState(traceState string) ([]dbmodel.KeyValue, bool) {\n\tvar keyValues []dbmodel.KeyValue\n\texists := traceState != \"\"\n\tif exists {\n\t\t// TODO Bring this inline with solution for jaegertracing/jaeger-client-java #702 once available\n\t\tkv := dbmodel.KeyValue{\n\t\t\tKey:         tagW3CTraceState,\n\t\t\tValueString: traceState,\n\t\t\tValueType:   dbmodel.StringType,\n\t\t}\n\t\tkeyValues = append(keyValues, kv)\n\t}\n\treturn keyValues, exists\n}\n\nfunc getTagsFromInstrumentationLibrary(scope pcommon.InstrumentationScope) ([]dbmodel.KeyValue, bool) {\n\tvar keyValues []dbmodel.KeyValue\n\tif ilName := scope.Name(); ilName != \"\" {\n\t\tkv := dbmodel.KeyValue{\n\t\t\tKey:         otelsemconv.AttributeOtelScopeName,\n\t\t\tValueString: ilName,\n\t\t\tValueType:   dbmodel.StringType,\n\t\t}\n\t\tkeyValues = append(keyValues, kv)\n\t}\n\tif ilVersion := scope.Version(); ilVersion != \"\" {\n\t\tkv := dbmodel.KeyValue{\n\t\t\tKey:         otelsemconv.AttributeOtelScopeVersion,\n\t\t\tValueString: ilVersion,\n\t\t\tValueType:   dbmodel.StringType,\n\t\t}\n\t\tkeyValues = append(keyValues, kv)\n\t}\n\n\treturn keyValues, len(keyValues) > 0\n}\n\nfunc dbRefTypeFromLink(link ptrace.SpanLink) string {\n\trefTypeAttr, ok := link.Attributes().Get(otelsemconv.AttributeOpentracingRefType)\n\tif !ok {\n\t\treturn dbmodel.FollowsFrom\n\t}\n\tattr := refTypeAttr.Str()\n\tif attr == otelsemconv.AttributeOpentracingRefTypeChildOf {\n\t\treturn dbmodel.ChildOf\n\t}\n\t// There are only 2 types of SpanRefType we assume that everything\n\t// that's not a model.ChildOf is a model.FollowsFrom\n\treturn dbmodel.FollowsFrom\n}\n"
  },
  {
    "path": "internal/storage/v2/cassandra/tracestore/to_dbmodel_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/traces_to_jaegerproto_test.go\n\npackage tracestore\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/cassandra/spanstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nfunc TestGetTagFromStatusCode(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcode ptrace.StatusCode\n\t\ttag  dbmodel.KeyValue\n\t}{\n\t\t{\n\t\t\tname: \"ok\",\n\t\t\tcode: ptrace.StatusCodeOk,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:         otelsemconv.OtelStatusCode,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: statusOk,\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tname: \"error\",\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:       tagError,\n\t\t\t\tValueType: dbmodel.BoolType,\n\t\t\t\tValueBool: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot, ok := getTagFromStatusCode(test.code)\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, test.tag, got)\n\t\t})\n\t}\n}\n\nfunc TestEmptyAttributes(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspans := traces.ResourceSpans().AppendEmpty()\n\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\tspanScope := scopeSpans.Scope()\n\tspan := scopeSpans.Spans().AppendEmpty()\n\tmodelSpan := spanToDbSpan(span, spanScope, dbmodel.Process{})\n\tassert.Empty(t, modelSpan.Tags)\n}\n\nfunc TestEmptyLinkRefs(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspans := traces.ResourceSpans().AppendEmpty()\n\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\tspanScope := scopeSpans.Scope()\n\tspan := scopeSpans.Spans().AppendEmpty()\n\tspanLink := span.Links().AppendEmpty()\n\tspanLink.Attributes().PutStr(\"testing-key\", \"testing-value\")\n\tmodelSpan := spanToDbSpan(span, spanScope, dbmodel.Process{})\n\tassert.Len(t, modelSpan.Refs, 1)\n\tassert.Equal(t, dbmodel.FollowsFrom, modelSpan.Refs[0].RefType)\n}\n\nfunc TestGetTagFromStatusMsg(t *testing.T) {\n\t_, ok := getTagFromStatusMsg(\"\")\n\tassert.False(t, ok)\n\n\tgot, ok := getTagFromStatusMsg(\"test-error\")\n\tassert.True(t, ok)\n\tassert.Equal(t, dbmodel.KeyValue{\n\t\tKey:         otelsemconv.OtelStatusDescription,\n\t\tValueString: \"test-error\",\n\t\tValueType:   dbmodel.StringType,\n\t}, got)\n}\n\nfunc Test_resourceToDbProcess_WhenOnlyServiceNameIsPresent(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspans := traces.ResourceSpans().AppendEmpty()\n\tspans.Resource().Attributes().PutStr(otelsemconv.ServiceNameKey, \"service\")\n\tprocess := resourceToDbProcess(spans.Resource())\n\tassert.Equal(t, \"service\", process.ServiceName)\n}\n\nfunc Test_resourceToDbProcess_DefaultServiceName(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspans := traces.ResourceSpans().AppendEmpty()\n\tspans.Resource().Attributes().PutStr(\"some attribute\", \"some value\")\n\tprocess := resourceToDbProcess(spans.Resource())\n\tassert.Equal(t, noServiceName, process.ServiceName)\n}\n\nfunc TestGetTagFromSpanKind(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tkind ptrace.SpanKind\n\t\ttag  dbmodel.KeyValue\n\t\tok   bool\n\t}{\n\t\t{\n\t\t\tname: \"unspecified\",\n\t\t\tkind: ptrace.SpanKindUnspecified,\n\t\t\ttag:  dbmodel.KeyValue{},\n\t\t\tok:   false,\n\t\t},\n\n\t\t{\n\t\t\tname: \"client\",\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:         model.SpanKindKey,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: string(model.SpanKindClient),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\n\t\t{\n\t\t\tname: \"server\",\n\t\t\tkind: ptrace.SpanKindServer,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:         model.SpanKindKey,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: string(model.SpanKindServer),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\n\t\t{\n\t\t\tname: \"producer\",\n\t\t\tkind: ptrace.SpanKindProducer,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:         model.SpanKindKey,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: string(model.SpanKindProducer),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\n\t\t{\n\t\t\tname: \"consumer\",\n\t\t\tkind: ptrace.SpanKindConsumer,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:         model.SpanKindKey,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: string(model.SpanKindConsumer),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\n\t\t{\n\t\t\tname: \"internal\",\n\t\t\tkind: ptrace.SpanKindInternal,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:         model.SpanKindKey,\n\t\t\t\tValueType:   dbmodel.StringType,\n\t\t\t\tValueString: string(model.SpanKindInternal),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot, ok := getTagFromSpanKind(test.kind)\n\t\t\tassert.Equal(t, test.ok, ok)\n\t\t\tassert.Equal(t, test.tag, got)\n\t\t})\n\t}\n}\n\nfunc TestAttributesToDbTags(t *testing.T) {\n\tattributes := pcommon.NewMap()\n\tattributes.PutBool(\"bool-val\", true)\n\tattributes.PutInt(\"int-val\", 123)\n\tattributes.PutStr(\"string-val\", \"abc\")\n\tattributes.PutDouble(\"double-val\", 1.23)\n\tattributes.PutEmptyBytes(\"bytes-val\").FromRaw([]byte{1, 2, 3, 4})\n\tattributes.PutStr(otelsemconv.ServiceNameKey, \"service-name\")\n\n\texpected := []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:       \"bool-val\",\n\t\t\tValueType: dbmodel.BoolType,\n\t\t\tValueBool: true,\n\t\t},\n\t\t{\n\t\t\tKey:        \"int-val\",\n\t\t\tValueType:  dbmodel.Int64Type,\n\t\t\tValueInt64: 123,\n\t\t},\n\t\t{\n\t\t\tKey:         \"string-val\",\n\t\t\tValueType:   dbmodel.StringType,\n\t\t\tValueString: \"abc\",\n\t\t},\n\t\t{\n\t\t\tKey:          \"double-val\",\n\t\t\tValueType:    dbmodel.Float64Type,\n\t\t\tValueFloat64: 1.23,\n\t\t},\n\t\t{\n\t\t\tKey:         \"bytes-val\",\n\t\t\tValueType:   dbmodel.BinaryType,\n\t\t\tValueBinary: []byte{1, 2, 3, 4},\n\t\t},\n\t\t{\n\t\t\tKey:         otelsemconv.ServiceNameKey,\n\t\t\tValueType:   dbmodel.StringType,\n\t\t\tValueString: \"service-name\",\n\t\t},\n\t}\n\n\tgot := appendTagsFromAttributes(make([]dbmodel.KeyValue, 0, len(expected)), attributes)\n\trequire.Equal(t, expected, got)\n}\n\nfunc TestAttributesToJaegerProtoTags_MapType(t *testing.T) {\n\tattributes := pcommon.NewMap()\n\tattributes.PutEmptyMap(\"empty-map\")\n\tgot := appendTagsFromAttributes(make([]dbmodel.KeyValue, 0, 1), attributes)\n\texpected := []dbmodel.KeyValue{\n\t\t{\n\t\t\tKey:         \"empty-map\",\n\t\t\tValueType:   dbmodel.StringType,\n\t\t\tValueString: \"{}\",\n\t\t},\n\t}\n\trequire.Equal(t, expected, got)\n}\n\nfunc BenchmarkInternalTracesToDbModel(b *testing.B) {\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\tdata, err := os.ReadFile(\"fixtures/otel_traces_01.json\")\n\trequire.NoError(b, err)\n\ttd, err := unmarshaller.UnmarshalTraces(data)\n\trequire.NoError(b, err)\n\n\tb.ResetTimer()\n\tfor n := 0; n < b.N; n++ {\n\t\tbatches := ToDBModel(td)\n\t\tassert.NotEmpty(b, batches)\n\t}\n}\n\nfunc TestToDbModel_Fixtures(t *testing.T) {\n\ttracesData, spansData := loadFixtures(t, 1)\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\texpectedTd, err := unmarshaller.UnmarshalTraces(tracesData)\n\trequire.NoError(t, err)\n\tbatches := ToDBModel(expectedTd)\n\tassert.Len(t, batches, 1)\n\ttestSpans(t, spansData, batches[0])\n\tactualTd := FromDBModel(batches)\n\ttestTraces(t, tracesData, actualTd)\n}\n\nfunc TestEdgeCases(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tsetupTraces func() ptrace.Traces\n\t\texpected    any\n\t\ttestFunc    func(ptrace.Traces) any\n\t\tdescription string\n\t}{\n\t\t{\n\t\t\tname: \"empty span attributes\",\n\t\t\tsetupTraces: func() ptrace.Traces {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\tscopeSpans.Spans().AppendEmpty()\n\t\t\t\treturn traces\n\t\t\t},\n\t\t\texpected: true,\n\t\t\ttestFunc: func(traces ptrace.Traces) any {\n\t\t\t\tmodelSpan := ToDBModel(traces)[0]\n\t\t\t\treturn len(modelSpan.Tags) == 0 && len(modelSpan.Process.Tags) == 0 && modelSpan.Process.ServiceName == noServiceName\n\t\t\t},\n\t\t\tdescription: \"Empty span attributes should result in no tags\",\n\t\t},\n\t\t{\n\t\t\tname: \"resource spans with no scope spans\",\n\t\t\tsetupTraces: func() ptrace.Traces {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\ttraces.ResourceSpans().AppendEmpty()\n\t\t\t\treturn traces\n\t\t\t},\n\t\t\texpected: true,\n\t\t\ttestFunc: func(traces ptrace.Traces) any {\n\t\t\t\tdbSpans := ToDBModel(traces)\n\t\t\t\treturn len(dbSpans) == 0\n\t\t\t},\n\t\t\tdescription: \"Resource spans with no scope spans should return empty slice\",\n\t\t},\n\t\t{\n\t\t\tname:        \"traces with no resource spans\",\n\t\t\tsetupTraces: ptrace.NewTraces,\n\t\t\texpected:    true,\n\t\t\ttestFunc: func(traces ptrace.Traces) any {\n\t\t\t\tdbSpans := ToDBModel(traces)\n\t\t\t\treturn len(dbSpans) == 0\n\t\t\t},\n\t\t\tdescription: \"Traces with no resource spans should return empty slice\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttraces := tt.setupTraces()\n\t\t\tresult := tt.testFunc(traces)\n\t\t\tassert.Equal(t, tt.expected, result, tt.description)\n\t\t})\n\t}\n}\n\nfunc writeActualData(t *testing.T, name string, data []byte) {\n\tvar prettyJson bytes.Buffer\n\terr := json.Indent(&prettyJson, data, \"\", \"  \")\n\trequire.NoError(t, err)\n\tpath := \"fixtures/actual_\" + name + \".json\"\n\terr = os.WriteFile(path, prettyJson.Bytes(), 0o644)\n\trequire.NoError(t, err)\n\tt.Log(\"Saved the actual \" + name + \" to \" + path)\n}\n\n// Loads and returns domain model and JSON model fixtures with given number i.\nfunc loadFixtures(t *testing.T, i int) (tracesData []byte, spansData []byte) {\n\ttracesData = loadTraces(t, i)\n\tspansData = loadSpans(t, i)\n\treturn tracesData, spansData\n}\n\nfunc loadTraces(t *testing.T, i int) []byte {\n\tinTraces := fmt.Sprintf(\"fixtures/otel_traces_%02d.json\", i)\n\ttracesData, err := os.ReadFile(inTraces)\n\trequire.NoError(t, err)\n\treturn tracesData\n}\n\nfunc loadSpans(t *testing.T, i int) []byte {\n\tinSpans := fmt.Sprintf(\"fixtures/cas_%02d.json\", i)\n\tspansData, err := os.ReadFile(inSpans)\n\trequire.NoError(t, err)\n\treturn spansData\n}\n\nfunc testTraces(t *testing.T, expectedTraces []byte, actualTraces ptrace.Traces) {\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\texpectedTd, err := unmarshaller.UnmarshalTraces(expectedTraces)\n\trequire.NoError(t, err)\n\tif !assert.Equal(t, expectedTd, actualTraces) {\n\t\tmarshaller := ptrace.JSONMarshaler{}\n\t\tactualTd, err := marshaller.MarshalTraces(actualTraces)\n\t\trequire.NoError(t, err)\n\t\twriteActualData(t, \"traces\", actualTd)\n\t}\n}\n\nfunc testSpans(t *testing.T, expectedSpan []byte, actualSpan dbmodel.Span) {\n\tbuf := &bytes.Buffer{}\n\tenc := json.NewEncoder(buf)\n\tenc.SetIndent(\"\", \"  \")\n\trequire.NoError(t, enc.Encode(actualSpan))\n\tif !assert.Equal(t, string(expectedSpan), buf.String()) {\n\t\twriteActualData(t, \"spans\", buf.Bytes())\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/README.md",
    "content": "## Clickhouse\n\n### Differences from the implementation in [otel collector contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/clickhouseexporter)\n\n#### Trace Storage Format\n\nThe most significant difference lies in the handling of **Attributes**. In the OTel-contrib implementation, everything within the Attributes is converted to [strings](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/80b3df26b7028a4bbe1eb606a6142cd4df9c3c74/exporter/clickhouseexporter/internal/metrics_model.go#L171-L177):\n\n```golang\nfunc AttributesToMap(attributes pcommon.Map) column.IterableOrderedMap {\n\treturn orderedmap.CollectN(func(yield func(string, string) bool) {\n\t\tfor k, v := range attributes.All() {\n\t\t\tyield(k, v.AsString())\n\t\t}\n\t}, attributes.Len())\n}\n```\n\nThe primary reason for this is that it leads to the loss of the original data types and \\~\\~cannot be used directly as query parameters\\~\\~ (Clickhouse provides casting functions). For example, if an attribute has an `int64` value, we might want to perform the following operation:\n\n```sql\nSELECT * FROM test WHERE resource.attributes['container.restart.count'] > 10\n```\n\nTo address the above issues, the following improvements have been implemented:\n\n  * Instead of directly using a Map for storage, the key and value are split into two separate arrays.\n  * More Columns are used to store values of different types:\n      * For basic types like bool, double, int, and string, corresponding type array columns are used for storage: `Array(Int64)`, `Array(Bool)`, etc.\n      * For complex types like slice and map, they are serialized into JSON format strings before storage: `Array(String)`.\n\nThe `Value` type here actually refers to the `pdata` data types from the `otel-collector` pipeline. In our architecture, the `value_warpper` is responsible for wrapping the Protobuf-generated Go structures (which are the concrete implementation of `pdata`) into the `Value` type. Although `pdata` itself is based on the OTLP specification, encapsulating it into `Value` via the `value_warpper` creates a higher-level abstraction, which presents some challenges for directly storing `Value` in ClickHouse. Specifically, when deserializing `Slice` and `Map` data contained within the `Value`, the fact that JSON cannot natively distinguish whether a `Number` is an integer (`int`) or a floating-point number (`double`) leads to a loss of type information. Furthermore, directly handling the potentially dynamically nested `pdata` structures within the `Value` can also be quite complex. Therefore, to ensure the accuracy and completeness of data types in ClickHouse, and to effectively handle these nested telemetry data, we need to convert the `pdata` data inside `Value` into the standard `OTLP/JSON` format for storage.\n\n#### Data Read and Write Methods\n\nThe OTel-contrib implementation uses `database/sql` for writing data. Using the provided generic interface is unnecessary; using the client provided by Clickhouse is a better choice.\nFor write operations, `ch-go`'s `chpool` is used in batch mode. For read operations, `clickhouse-go` is used."
  },
  {
    "path": "internal/storage/v2/clickhouse/clickhousetest/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhousetest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/clickhousetest/server.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhousetest\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/ClickHouse/ch-go/proto\"\n\t\"github.com/ClickHouse/clickhouse-go/v2\"\n\tchproto \"github.com/ClickHouse/clickhouse-go/v2/lib/proto\"\n)\n\nvar (\n\tPingQuery      = \"SELECT 1\"\n\tHandshakeQuery = \"SELECT displayName(), version(), revision(), timezone()\"\n)\n\n// FailureConfig is a map of query body to error\ntype FailureConfig map[string]error\n\n// NewServer creates a new HTTP test server that simulates a ClickHouse server.\n// It should only be used in tests.\nfunc NewServer(failures FailureConfig) *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\tquery := string(body)\n\n\t\tblock := chproto.NewBlock()\n\n\t\tif err, shouldFail := failures[query]; shouldFail {\n\t\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tswitch query {\n\t\tcase PingQuery:\n\t\t\tblock.AddColumn(\"1\", \"UInt8\")\n\t\t\tblock.Append(uint8(1))\n\t\tcase HandshakeQuery:\n\t\t\tblock.AddColumn(\"displayName()\", \"String\")\n\t\t\tblock.AddColumn(\"version()\", \"String\")\n\t\t\tblock.AddColumn(\"revision()\", \"UInt32\")\n\t\t\tblock.AddColumn(\"timezone()\", \"String\")\n\t\t\tblock.Append(\"mock-server\", \"23.3.1\", chproto.DBMS_MIN_REVISION_WITH_CUSTOM_SERIALIZATION, \"UTC\")\n\t\tdefault:\n\t\t}\n\n\t\tvar buf proto.Buffer\n\t\tblock.Encode(&buf, clickhouse.ClientTCPProtocolVersion)\n\n\t\tw.Header().Set(\"Content-Type\", \"application/octet-stream\")\n\t\tw.Write(buf.Buf)\n\t}))\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/config.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhouse\n\nimport (\n\t\"time\"\n\n\t\"github.com/asaskevich/govalidator\"\n\t\"github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n)\n\nconst (\n\tdefaultProtocol       = \"native\"\n\tdefaultDatabase       = \"jaeger\"\n\tdefaultSearchDepth    = 1000\n\tdefaultMaxSearchDepth = 10000\n)\n\ntype Configuration struct {\n\t// Protocol is the protocol to use to connect to ClickHouse.\n\t// Supported values are \"native\" and \"http\". Default is \"native\".\n\tProtocol string `mapstructure:\"protocol\" valid:\"in(native|http),optional\"`\n\t// Addresses contains a list of ClickHouse server addresses to connect to.\n\tAddresses []string `mapstructure:\"addresses\" valid:\"required\"`\n\t// Database is the ClickHouse database to connect to.\n\tDatabase string `mapstructure:\"database\"`\n\t// Auth contains the authentication configuration to connect to ClickHouse.\n\tAuth Authentication `mapstructure:\"auth\"`\n\t// DialTimeout is the timeout for establishing a connection to ClickHouse.\n\tDialTimeout time.Duration `mapstructure:\"dial_timeout\"`\n\t// CreateSchema, if set to true, will create the ClickHouse schema if it does not exist.\n\tCreateSchema bool `mapstructure:\"create_schema\"`\n\t// DefaultSearchDepth is the default search depth for queries.\n\t// This is the maximum number of trace IDs that will be returned when searching for traces\n\t// if a limit is not specified in the query.\n\tDefaultSearchDepth int `mapstructure:\"default_search_depth\"`\n\t// MaxSearchDepth is the maximum allowed search depth for queries.\n\t// This limits the number of trace IDs that can be returned when searching for traces.\n\tMaxSearchDepth int `mapstructure:\"max_search_depth\"`\n\t// TODO: add more settings\n}\n\ntype Authentication struct {\n\tBasic configoptional.Optional[basicauthextension.ClientAuthSettings] `mapstructure:\"basic\"`\n\t// TODO: add JWT\n}\n\nfunc (cfg *Configuration) Validate() error {\n\t_, err := govalidator.ValidateStruct(cfg)\n\treturn err\n}\n\nfunc (cfg *Configuration) applyDefaults() {\n\tif cfg.Protocol == \"\" {\n\t\tcfg.Protocol = \"native\"\n\t}\n\tif cfg.Database == \"\" {\n\t\tcfg.Database = defaultDatabase\n\t}\n\tif cfg.DefaultSearchDepth == 0 {\n\t\tcfg.DefaultSearchDepth = defaultSearchDepth\n\t}\n\tif cfg.MaxSearchDepth == 0 {\n\t\tcfg.MaxSearchDepth = defaultMaxSearchDepth\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/config_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhouse\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidate(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tcfg     Configuration\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid config with native protocol\",\n\t\t\tcfg: Configuration{\n\t\t\t\tProtocol:  \"native\",\n\t\t\t\tAddresses: []string{\"localhost:9000\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid config with http protocol\",\n\t\t\tcfg: Configuration{\n\t\t\t\tProtocol:  \"http\",\n\t\t\t\tAddresses: []string{\"localhost:8123\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid config with empty protocol\",\n\t\t\tcfg: Configuration{\n\t\t\t\tAddresses: []string{\"localhost:9000\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid config with multiple addresses\",\n\t\t\tcfg: Configuration{\n\t\t\t\tProtocol:  \"native\",\n\t\t\t\tAddresses: []string{\"localhost:9000\", \"localhost:9001\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid config with unsupported protocol\",\n\t\t\tcfg: Configuration{\n\t\t\t\tProtocol:  \"grpc\",\n\t\t\t\tAddresses: []string{\"localhost:9000\"},\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid config with empty addresses\",\n\t\t\tcfg: Configuration{\n\t\t\t\tProtocol:  \"native\",\n\t\t\t\tAddresses: []string{},\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid config with nil addresses\",\n\t\t\tcfg: Configuration{\n\t\t\t\tProtocol: \"native\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.cfg.Validate()\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConfigurationApplyDefaults(t *testing.T) {\n\tconfig := &Configuration{}\n\tconfig.applyDefaults()\n\n\trequire.Equal(t, defaultProtocol, config.Protocol)\n\trequire.Equal(t, defaultDatabase, config.Database)\n\trequire.Equal(t, defaultSearchDepth, config.DefaultSearchDepth)\n\trequire.Equal(t, defaultMaxSearchDepth, config.MaxSearchDepth)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/depstore/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/depstore/reader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n)\n\nvar _ depstore.Reader = (*Reader)(nil)\n\ntype Reader struct{}\n\nfunc NewDependencyReader() *Reader {\n\treturn &Reader{}\n}\n\nfunc (*Reader) GetDependencies(context.Context, depstore.QueryParameters) ([]model.DependencyLink, error) {\n\tpanic(\"not implemented\")\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/depstore/reader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n)\n\nfunc TestReader_GetDependencies(t *testing.T) {\n\treader := NewDependencyReader()\n\tctx := context.Background()\n\tquery := depstore.QueryParameters{}\n\n\trequire.Panics(t, func() {\n\t\treader.GetDependencies(ctx, query)\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhouse\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ClickHouse/clickhouse-go/v2\"\n\t\"github.com/ClickHouse/clickhouse-go/v2/lib/driver\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tchdepstore \"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\tchtracestore \"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nvar (\n\t_ io.Closer          = (*Factory)(nil)\n\t_ depstore.Factory   = (*Factory)(nil)\n\t_ tracestore.Factory = (*Factory)(nil)\n\t_ storage.Purger     = (*Factory)(nil)\n)\n\ntype Factory struct {\n\tconfig Configuration\n\ttelset telemetry.Settings\n\tconn   driver.Conn\n}\n\nfunc NewFactory(ctx context.Context, cfg Configuration, telset telemetry.Settings) (*Factory, error) {\n\tcfg.applyDefaults()\n\tf := &Factory{\n\t\tconfig: cfg,\n\t\ttelset: telset,\n\t}\n\topts := &clickhouse.Options{\n\t\tProtocol: getProtocol(f.config.Protocol),\n\t\tAddr:     f.config.Addresses,\n\t\tAuth: clickhouse.Auth{\n\t\t\tDatabase: f.config.Database,\n\t\t},\n\t\tDialTimeout: f.config.DialTimeout,\n\t}\n\tbasicAuth := f.config.Auth.Basic.Get()\n\tif basicAuth != nil {\n\t\topts.Auth.Username = basicAuth.Username\n\t\topts.Auth.Password = string(basicAuth.Password)\n\t}\n\tconn, err := clickhouse.Open(opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create ClickHouse connection: %w\", err)\n\t}\n\terr = conn.Ping(ctx)\n\tif err != nil {\n\t\treturn nil, errors.Join(\n\t\t\tfmt.Errorf(\"failed to ping ClickHouse: %w\", err),\n\t\t\tconn.Close(),\n\t\t)\n\t}\n\tif f.config.CreateSchema {\n\t\tschemas := []struct {\n\t\t\tname  string\n\t\t\tquery string\n\t\t}{\n\t\t\t{\"spans table\", sql.CreateSpansTable},\n\t\t\t{\"services table\", sql.CreateServicesTable},\n\t\t\t{\"services materialized view\", sql.CreateServicesMaterializedView},\n\t\t\t{\"operations table\", sql.CreateOperationsTable},\n\t\t\t{\"operations materialized view\", sql.CreateOperationsMaterializedView},\n\t\t\t{\"trace id timestamps table\", sql.CreateTraceIDTimestampsTable},\n\t\t\t{\"trace id timestamps materialized view\", sql.CreateTraceIDTimestampsMaterializedView},\n\t\t\t{\"attribute metadata table\", sql.CreateAttributeMetadataTable},\n\t\t\t{\"attribute metadata materialized view\", sql.CreateAttributeMetadataMaterializedView},\n\t\t\t{\"event attribute metadata materialized view\", sql.CreateEventAttributeMetadataMaterializedView},\n\t\t\t{\"link attribute metadata materialized view\", sql.CreateLinkAttributeMetadataMaterializedView},\n\t\t}\n\n\t\tfor _, schema := range schemas {\n\t\t\tif err = conn.Exec(ctx, schema.query); err != nil {\n\t\t\t\treturn nil, errors.Join(fmt.Errorf(\"failed to create %s: %w\", schema.name, err), conn.Close())\n\t\t\t}\n\t\t}\n\t}\n\tf.conn = conn\n\treturn f, nil\n}\n\nfunc (f *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\treturn chtracestore.NewReader(f.conn, chtracestore.ReaderConfig{\n\t\tDefaultSearchDepth: f.config.DefaultSearchDepth,\n\t\tMaxSearchDepth:     f.config.MaxSearchDepth,\n\t}), nil\n}\n\nfunc (f *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\treturn chtracestore.NewWriter(f.conn), nil\n}\n\nfunc (*Factory) CreateDependencyReader() (depstore.Reader, error) {\n\treturn chdepstore.NewDependencyReader(), nil\n}\n\nfunc (f *Factory) Close() error {\n\treturn f.conn.Close()\n}\n\nfunc (f *Factory) Purge(ctx context.Context) error {\n\ttables := []struct {\n\t\tname  string\n\t\tquery string\n\t}{\n\t\t{\"spans\", sql.TruncateSpans},\n\t\t{\"services\", sql.TruncateServices},\n\t\t{\"operations\", sql.TruncateOperations},\n\t\t{\"trace_id_timestamps\", sql.TruncateTraceIDTimestamps},\n\t\t{\"attribute_metadata\", sql.TruncateAttributeMetadata},\n\t}\n\n\tfor _, table := range tables {\n\t\tif err := f.conn.Exec(ctx, table.query); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to purge %s: %w\", table.name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getProtocol(protocol string) clickhouse.Protocol {\n\tif protocol == \"http\" {\n\t\treturn clickhouse.HTTP\n\t}\n\treturn clickhouse.Native\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhouse\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/ClickHouse/clickhouse-go/v2\"\n\t\"github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/clickhousetest\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nfunc TestFactory(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tcreateSchema bool\n\t}{\n\t\t{\n\t\t\tname:         \"without schema creation\",\n\t\t\tcreateSchema: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"with schema creation\",\n\t\t\tcreateSchema: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsrv := clickhousetest.NewServer(clickhousetest.FailureConfig{})\n\t\t\tdefer srv.Close()\n\n\t\t\tcfg := Configuration{\n\t\t\t\tProtocol: \"http\",\n\t\t\t\tAddresses: []string{\n\t\t\t\t\tsrv.Listener.Addr().String(),\n\t\t\t\t},\n\t\t\t\tDatabase: \"default\",\n\t\t\t\tAuth: Authentication{\n\t\t\t\t\tBasic: configoptional.Some(basicauthextension.ClientAuthSettings{\n\t\t\t\t\t\tUsername: \"user\",\n\t\t\t\t\t\tPassword: \"password\",\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tf, err := NewFactory(context.Background(), cfg, telemetry.Settings{})\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, f)\n\n\t\t\ttr, err := f.CreateTraceReader()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, tr)\n\n\t\t\ttw, err := f.CreateTraceWriter()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, tw)\n\n\t\t\tdr, err := f.CreateDependencyReader()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, dr)\n\n\t\t\terr = f.Purge(context.Background())\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.NoError(t, f.Close())\n\t\t})\n\t}\n}\n\nfunc TestNewFactory_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tfailureConfig clickhousetest.FailureConfig\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"ping error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tclickhousetest.PingQuery: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to ping ClickHouse\",\n\t\t},\n\t\t{\n\t\t\tname: \"spans table creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateSpansTable: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create spans table\",\n\t\t},\n\t\t{\n\t\t\tname: \"services table creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateServicesTable: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create services table\",\n\t\t},\n\t\t{\n\t\t\tname: \"services materialized view creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateServicesMaterializedView: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create services materialized view\",\n\t\t},\n\t\t{\n\t\t\tname: \"operations table creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateOperationsTable: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create operations table\",\n\t\t},\n\t\t{\n\t\t\tname: \"operations materialized view creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateOperationsMaterializedView: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create operations materialized view\",\n\t\t},\n\t\t{\n\t\t\tname: \"trace id timestamps table creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateTraceIDTimestampsTable: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create trace id timestamps table\",\n\t\t},\n\t\t{\n\t\t\tname: \"trace id timestamps materialized view creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateTraceIDTimestampsMaterializedView: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create trace id timestamps materialized view\",\n\t\t},\n\t\t{\n\t\t\tname: \"attribute metadata table creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateAttributeMetadataTable: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create attribute metadata table\",\n\t\t},\n\t\t{\n\t\t\tname: \"attribute metadata materialized view creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateAttributeMetadataMaterializedView: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create attribute metadata materialized view\",\n\t\t},\n\t\t{\n\t\t\tname: \"event attribute metadata materialized view creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateEventAttributeMetadataMaterializedView: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create event attribute metadata materialized view\",\n\t\t},\n\t\t{\n\t\t\tname: \"link attribute metadata materialized view creation error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.CreateLinkAttributeMetadataMaterializedView: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to create link attribute metadata materialized view\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsrv := clickhousetest.NewServer(tt.failureConfig)\n\t\t\tdefer srv.Close()\n\n\t\t\tcfg := Configuration{\n\t\t\t\tProtocol: \"http\",\n\t\t\t\tAddresses: []string{\n\t\t\t\t\tsrv.Listener.Addr().String(),\n\t\t\t\t},\n\t\t\t\tDialTimeout:  1 * time.Second,\n\t\t\t\tCreateSchema: true,\n\t\t\t}\n\n\t\t\tf, err := NewFactory(context.Background(), cfg, telemetry.Settings{})\n\t\t\trequire.ErrorContains(t, err, tt.expectedError)\n\t\t\trequire.Nil(t, f)\n\t\t})\n\t}\n}\n\nfunc TestPurge(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tfailureConfig clickhousetest.FailureConfig\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"truncate spans table error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.TruncateSpans: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to purge spans\",\n\t\t},\n\t\t{\n\t\t\tname: \"truncate services table error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.TruncateServices: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to purge services\",\n\t\t},\n\t\t{\n\t\t\tname: \"truncate operations table error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.TruncateOperations: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to purge operations\",\n\t\t},\n\t\t{\n\t\t\tname: \"truncate trace_id_timestamps table error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.TruncateTraceIDTimestamps: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to purge trace_id_timestamps\",\n\t\t},\n\t\t{\n\t\t\tname: \"truncate attribute_metadata table error\",\n\t\t\tfailureConfig: clickhousetest.FailureConfig{\n\t\t\t\tsql.TruncateAttributeMetadata: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to purge attribute_metadata\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsrv := clickhousetest.NewServer(tt.failureConfig)\n\t\t\tdefer srv.Close()\n\n\t\t\tcfg := Configuration{\n\t\t\t\tProtocol: \"http\",\n\t\t\t\tAddresses: []string{\n\t\t\t\t\tsrv.Listener.Addr().String(),\n\t\t\t\t},\n\t\t\t\tDialTimeout:  1 * time.Second,\n\t\t\t\tCreateSchema: true,\n\t\t\t}\n\n\t\t\tf, err := NewFactory(context.Background(), cfg, telemetry.Settings{})\n\t\t\trequire.NoError(t, err)\n\t\t\tt.Cleanup(func() {\n\t\t\t\trequire.NoError(t, f.Close())\n\t\t\t})\n\n\t\t\terr = f.Purge(context.Background())\n\t\t\trequire.ErrorContains(t, err, tt.expectedError)\n\t\t})\n\t}\n}\n\nfunc TestGetProtocol(t *testing.T) {\n\ttests := []struct {\n\t\tprotocol string\n\t\texpected clickhouse.Protocol\n\t}{\n\t\t{\n\t\t\tprotocol: \"http\",\n\t\t\texpected: clickhouse.HTTP,\n\t\t},\n\t\t{\n\t\t\tprotocol: \"native\",\n\t\t\texpected: clickhouse.Native,\n\t\t},\n\t\t{\n\t\t\tprotocol: \"\",\n\t\t\texpected: clickhouse.Native,\n\t\t},\n\t\t{\n\t\t\tprotocol: \"unknown\",\n\t\t\texpected: clickhouse.Native,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.protocol, func(t *testing.T) {\n\t\t\tresult := getProtocol(tt.protocol)\n\t\t\trequire.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage clickhouse\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_attribute_metadata_mv.sql",
    "content": "CREATE MATERIALIZED VIEW IF NOT EXISTS attribute_metadata_mv TO attribute_metadata AS\nSELECT\n    tp.1 AS attribute_key,\n    tp.2 AS type,\n    tp.3 AS level\nFROM (\n    SELECT\n        arrayJoin(arrayConcat(\n            arrayMap(k -> (k, 'bool',   'span'),     bool_attributes.key),\n            arrayMap(k -> (k, 'bool',   'resource'), resource_bool_attributes.key),\n            arrayMap(k -> (k, 'bool',   'scope'),    scope_bool_attributes.key),\n            arrayMap(k -> (k, 'double', 'span'),     double_attributes.key),\n            arrayMap(k -> (k, 'double', 'resource'), resource_double_attributes.key),\n            arrayMap(k -> (k, 'double', 'scope'),    scope_double_attributes.key),\n            arrayMap(k -> (k, 'int',    'span'),     int_attributes.key),\n            arrayMap(k -> (k, 'int',    'resource'), resource_int_attributes.key),\n            arrayMap(k -> (k, 'int',    'scope'),    scope_int_attributes.key),\n            arrayMap(k -> (k, 'str',    'span'),     str_attributes.key),\n            arrayMap(k -> (k, 'str',    'resource'), resource_str_attributes.key),\n            arrayMap(k -> (k, 'str',    'scope'),    scope_str_attributes.key),\n            arrayMap(k -> (\n                multiIf(startsWith(k, '@bytes@'), substring(k, 8),\n                        startsWith(k, '@map@'),   substring(k, 6),\n                        startsWith(k, '@slice@'), substring(k, 8), k),\n                multiIf(startsWith(k, '@bytes@'), 'bytes',\n                        startsWith(k, '@map@'),   'map',\n                        startsWith(k, '@slice@'), 'slice', ''),\n                'span'\n            ), complex_attributes.key),\n            arrayMap(k -> (\n                multiIf(startsWith(k, '@bytes@'), substring(k, 8),\n                        startsWith(k, '@map@'),   substring(k, 6),\n                        startsWith(k, '@slice@'), substring(k, 8), k),\n                multiIf(startsWith(k, '@bytes@'), 'bytes',\n                        startsWith(k, '@map@'),   'map',\n                        startsWith(k, '@slice@'), 'slice', ''),\n                'resource'\n            ), resource_complex_attributes.key),\n            arrayMap(k -> (\n                multiIf(startsWith(k, '@bytes@'), substring(k, 8),\n                        startsWith(k, '@map@'),   substring(k, 6),\n                        startsWith(k, '@slice@'), substring(k, 8), k),\n                multiIf(startsWith(k, '@bytes@'), 'bytes',\n                        startsWith(k, '@map@'),   'map',\n                        startsWith(k, '@slice@'), 'slice', ''),\n                'scope'\n            ), scope_complex_attributes.key)\n        )) AS tp\n    FROM spans\n)\nGROUP BY attribute_key, type, level;"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_attribute_metadata_table.sql",
    "content": "CREATE TABLE\n    IF NOT EXISTS attribute_metadata (\n        attribute_key String,\n        type String,  -- 'bool', 'double', 'int', 'str', 'bytes', 'map', 'slice'\n        level String  -- 'resource', 'scope', 'span'\n    ) ENGINE = AggregatingMergeTree\nORDER BY (attribute_key, type, level)\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_event_attribute_metadata_mv.sql",
    "content": "-- Events and links are stored in a nested list. If we processed them in the same view \n-- as the main span data, ClickHouse would have to create a \"duplicate\" row \n-- of the span for every single link found because of the ARRAY JOIN operation.\n-- Example: A span with 10 attributes and 5 links would turn into 5 rows \n-- of 10 attributes each, forcing the database to process 50 items instead of 15.\nCREATE MATERIALIZED VIEW IF NOT EXISTS event_attribute_metadata_mv TO attribute_metadata AS\nSELECT\n    tp.1 AS attribute_key,\n    tp.2 AS type,\n    'event' AS level\nFROM spans\nARRAY JOIN events AS e\nARRAY JOIN arrayConcat(\n    arrayMap(k -> (k, 'bool'),   e.bool_attributes.key),\n    arrayMap(k -> (k, 'double'), e.double_attributes.key),\n    arrayMap(k -> (k, 'int'),    e.int_attributes.key),\n    arrayMap(k -> (k, 'str'),    e.str_attributes.key),\n    arrayMap(k -> (\n        multiIf(startsWith(k, '@bytes@'), substring(k, 8), \n                startsWith(k, '@map@'),   substring(k, 6), \n                startsWith(k, '@slice@'), substring(k, 8), k),\n        multiIf(startsWith(k, '@bytes@'), 'bytes', \n                startsWith(k, '@map@'),   'map', \n                startsWith(k, '@slice@'), 'slice', '')\n    ), e.complex_attributes.key)\n) AS tp\nGROUP BY attribute_key, type, level;"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_link_attribute_metadata_mv.sql",
    "content": "CREATE MATERIALIZED VIEW IF NOT EXISTS link_attribute_metadata_mv TO attribute_metadata AS\nSELECT\n    tp.1 AS attribute_key,\n    tp.2 AS type,\n    'link' AS level\nFROM spans\nARRAY JOIN links AS l\nARRAY JOIN arrayConcat(\n    arrayMap(k -> (k, 'bool'),   l.bool_attributes.key),\n    arrayMap(k -> (k, 'double'), l.double_attributes.key),\n    arrayMap(k -> (k, 'int'),    l.int_attributes.key),\n    arrayMap(k -> (k, 'str'),    l.str_attributes.key),\n    arrayMap(k -> (\n        multiIf(startsWith(k, '@bytes@'), substring(k, 8), \n                startsWith(k, '@map@'),   substring(k, 6), \n                startsWith(k, '@slice@'), substring(k, 8), k),\n        multiIf(startsWith(k, '@bytes@'), 'bytes', \n                startsWith(k, '@map@'),   'map', \n                startsWith(k, '@slice@'), 'slice', '')\n    ), l.complex_attributes.key)\n) AS tp\nGROUP BY attribute_key, type, level;\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_operations_mv.sql",
    "content": "CREATE MATERIALIZED VIEW IF NOT EXISTS operations_mv TO operations AS\nSELECT\n    name,\n    kind AS span_kind,\n    service_name\nFROM\n    spans;"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_operations_table.sql",
    "content": "CREATE TABLE IF NOT EXISTS\n    operations (\n        service_name String,\n        name String,\n        span_kind String\n    ) ENGINE = AggregatingMergeTree\nORDER BY\n    (service_name, span_kind);"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_services_mv.sql",
    "content": "CREATE MATERIALIZED VIEW IF NOT EXISTS services_mv TO services AS\nSELECT\n    service_name AS name\nFROM\n    spans\nGROUP BY\n    service_name;"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_services_table.sql",
    "content": "CREATE TABLE\n    IF NOT EXISTS services (name String) ENGINE = AggregatingMergeTree\nORDER BY\n    (name);"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_spans_table.sql",
    "content": "CREATE TABLE\n    IF NOT EXISTS spans (\n        id String,\n        trace_id String,\n        trace_state String,\n        parent_span_id String,\n        name String,\n        kind String,\n        start_time DateTime64 (9),\n        status_code String,\n        status_message String,\n        duration Int64,\n        bool_attributes Nested (key String, value Bool),\n        double_attributes Nested (key String, value Float64),\n        int_attributes Nested (key String, value Int64),\n        str_attributes Nested (key String, value String),\n        complex_attributes Nested (key String, value String),\n        events Nested (\n            name String,\n            timestamp DateTime64 (9),\n            bool_attributes Nested (key String, value Bool),\n            double_attributes Nested (key String, value Float64),\n            int_attributes Nested (key String, value Int64),\n            str_attributes Nested (key String, value String),\n            complex_attributes Nested (key String, value String)\n        ),\n        links Nested (\n            trace_id String,\n            span_id String,\n            trace_state String,\n            bool_attributes Nested (key String, value Bool),\n            double_attributes Nested (key String, value Float64),\n            int_attributes Nested (key String, value Int64),\n            str_attributes Nested (key String, value String),\n            complex_attributes Nested (key String, value String)\n        ),\n        service_name String,\n        resource_bool_attributes Nested (key String, value Bool),\n        resource_double_attributes Nested (key String, value Float64),\n        resource_int_attributes Nested (key String, value Int64),\n        resource_str_attributes Nested (key String, value String),\n        resource_complex_attributes Nested (key String, value String),\n        scope_name String,\n        scope_version String,\n        scope_bool_attributes Nested (key String, value Bool),\n        scope_double_attributes Nested (key String, value Float64),\n        scope_int_attributes Nested (key String, value Int64),\n        scope_str_attributes Nested (key String, value String),\n        scope_complex_attributes Nested (key String, value String),\n        INDEX idx_service_name service_name TYPE set(500) GRANULARITY 1,\n        INDEX idx_name name TYPE set(1000) GRANULARITY 1,\n        INDEX idx_start_time start_time TYPE minmax GRANULARITY 1,\n        INDEX idx_duration duration TYPE minmax GRANULARITY 1,\n        INDEX idx_attributes_keys str_attributes.key TYPE bloom_filter GRANULARITY 1,\n        INDEX idx_attributes_values str_attributes.value TYPE bloom_filter GRANULARITY 1,\n        INDEX idx_resource_attributes_keys resource_str_attributes.key TYPE bloom_filter GRANULARITY 1,\n        INDEX idx_resource_attributes_values resource_str_attributes.value TYPE bloom_filter GRANULARITY 1,\n    ) ENGINE = MergeTree\nPARTITION BY toDate(start_time)\nORDER BY (trace_id)"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_trace_id_timestamps_mv.sql",
    "content": "CREATE MATERIALIZED VIEW IF NOT EXISTS trace_id_timestamps_mv\nTO trace_id_timestamps\nAS\nSELECT\n    trace_id,\n    min(start_time) AS start,\n    max(start_time) AS end\nFROM spans\nGROUP BY trace_id;"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/create_trace_id_timestamps_table.sql",
    "content": "CREATE TABLE IF NOT EXISTS trace_id_timestamps\n(\n    trace_id String,\n    start SimpleAggregateFunction(min, DateTime64(9)),\n    end SimpleAggregateFunction(max, DateTime64(9))\n)\nENGINE = AggregatingMergeTree()\nORDER BY (trace_id);"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sql\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/sql/queries.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage sql\n\nimport _ \"embed\"\n\nconst InsertSpan = `\nINSERT INTO\n    spans (\n        id,\n        trace_id,\n        trace_state,\n        parent_span_id,\n        name,\n        kind,\n        start_time,\n        status_code,\n        status_message,\n        duration,\n        bool_attributes.key,\n        bool_attributes.value,\n        double_attributes.key,\n        double_attributes.value,\n        int_attributes.key,\n        int_attributes.value,\n        str_attributes.key,\n        str_attributes.value,\n        complex_attributes.key,\n        complex_attributes.value,\n        events.name,\n        events.timestamp,\n        events.bool_attributes,\n        events.double_attributes,\n        events.int_attributes,\n        events.str_attributes,\n        events.complex_attributes,\n        links.trace_id,\n        links.span_id,\n        links.trace_state,\n        links.bool_attributes,\n        links.double_attributes,\n        links.int_attributes,\n        links.str_attributes,\n        links.complex_attributes,\n        service_name,\n        resource_bool_attributes.key,\n        resource_bool_attributes.value,\n        resource_double_attributes.key,\n        resource_double_attributes.value,\n        resource_int_attributes.key,\n        resource_int_attributes.value,\n        resource_str_attributes.key,\n        resource_str_attributes.value,\n        resource_complex_attributes.key,\n        resource_complex_attributes.value,\n        scope_name,\n        scope_version,\n        scope_bool_attributes.key,\n        scope_bool_attributes.value,\n        scope_double_attributes.key,\n        scope_double_attributes.value,\n        scope_int_attributes.key,\n        scope_int_attributes.value,\n        scope_str_attributes.key,\n        scope_str_attributes.value,\n        scope_complex_attributes.key,\n        scope_complex_attributes.value\n    )\nVALUES\n    (\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?\n    )\n`\n\nconst SelectSpansQuery = `\nSELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\n`\n\nconst SelectSpansByTraceID = SelectSpansQuery + \" WHERE s.trace_id = ?\"\n\n// SearchTraceIDsBase is the inner SQL fragment for finding distinct trace IDs.\n//\n// The query begins with a no-op predicate (`WHERE 1=1`) so that additional\n// filters can be appended unconditionally using `AND` without needing to check\n// whether this is the first WHERE clause.\nconst SearchTraceIDsBase = `SELECT DISTINCT\n    s.trace_id\nFROM spans s\nWHERE 1=1`\n\n// SearchTraceIDs wraps a trace ID subquery with a JOIN to\n// trace_id_timestamps to retrieve the start and end times for each trace.\n// The %s placeholder is replaced with the complete inner subquery\n// (SearchTraceIDsBase + conditions + LIMIT).\nconst SearchTraceIDs = `\nSELECT\n    l.trace_id,\n    min(t.start) AS start,\n    max(t.end) AS end\nFROM (\n%s\n) l\nLEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id\nGROUP BY l.trace_id`\n\nconst SelectServices = `\nSELECT\n    name\nFROM\n    services\nGROUP BY name\n`\n\nconst SelectOperationsAllKinds = `\nSELECT\n    name,\n    span_kind\nFROM\n    operations\nWHERE\n    service_name = ?\nGROUP BY name, span_kind\n`\n\nconst SelectOperationsByKind = `\nSELECT\n    name,\n    span_kind\nFROM\n    operations\nWHERE\n    service_name = ?\n    AND span_kind = ?\nGROUP BY name, span_kind\n`\n\nconst SelectAttributeMetadata = `\nSELECT\n    attribute_key,\n    type,\n    level\nFROM\n    attribute_metadata`\n\nconst TruncateSpans = `TRUNCATE TABLE spans`\n\nconst TruncateServices = `TRUNCATE TABLE services`\n\nconst TruncateOperations = `TRUNCATE TABLE operations`\n\nconst TruncateTraceIDTimestamps = `TRUNCATE TABLE trace_id_timestamps`\n\nconst TruncateAttributeMetadata = `TRUNCATE TABLE attribute_metadata`\n\n//go:embed create_spans_table.sql\nvar CreateSpansTable string\n\n//go:embed create_services_table.sql\nvar CreateServicesTable string\n\n//go:embed create_services_mv.sql\nvar CreateServicesMaterializedView string\n\n//go:embed create_operations_table.sql\nvar CreateOperationsTable string\n\n//go:embed create_operations_mv.sql\nvar CreateOperationsMaterializedView string\n\n//go:embed create_trace_id_timestamps_table.sql\nvar CreateTraceIDTimestampsTable string\n\n//go:embed create_trace_id_timestamps_mv.sql\nvar CreateTraceIDTimestampsMaterializedView string\n\n//go:embed create_attribute_metadata_table.sql\nvar CreateAttributeMetadataTable string\n\n//go:embed create_attribute_metadata_mv.sql\nvar CreateAttributeMetadataMaterializedView string\n\n//go:embed create_event_attribute_metadata_mv.sql\nvar CreateEventAttributeMetadataMaterializedView string\n\n//go:embed create_link_attribute_metadata_mv.sql\nvar CreateLinkAttributeMetadataMaterializedView string\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/assert_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"encoding/base64\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/xpdata\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\nfunc requireTracesEqual(t *testing.T, expected []*dbmodel.SpanRow, actual []ptrace.Traces) {\n\tt.Helper()\n\n\trequire.Len(t, actual, len(expected))\n\n\tfor i, e := range expected {\n\t\tresources := actual[i].ResourceSpans()\n\t\trequire.Equal(t, 1, resources.Len())\n\n\t\tscopes := resources.At(0).ScopeSpans()\n\t\trequire.Equal(t, 1, scopes.Len())\n\t\trequireScopeEqual(t, e, scopes.At(0).Scope())\n\n\t\tspans := scopes.At(0).Spans()\n\t\trequire.Equal(t, 1, spans.Len())\n\n\t\trequireSpanEqual(t, e, spans.At(0))\n\t}\n}\n\nfunc requireScopeEqual(t *testing.T, expected *dbmodel.SpanRow, actual pcommon.InstrumentationScope) {\n\tt.Helper()\n\n\trequire.Equal(t, expected.ScopeName, actual.Name())\n\trequire.Equal(t, expected.ScopeVersion, actual.Version())\n}\n\nfunc requireSpanEqual(t *testing.T, expected *dbmodel.SpanRow, actual ptrace.Span) {\n\tt.Helper()\n\n\trequire.Equal(t, expected.ID, actual.SpanID().String())\n\trequire.Equal(t, expected.TraceID, actual.TraceID().String())\n\trequire.Equal(t, expected.TraceState, actual.TraceState().AsRaw())\n\trequire.Equal(t, expected.ParentSpanID, actual.ParentSpanID().String())\n\trequire.Equal(t, expected.Name, actual.Name())\n\trequire.Equal(t, expected.Kind, jptrace.SpanKindToString(actual.Kind()))\n\trequire.Equal(t, expected.StartTime.UnixNano(), actual.StartTimestamp().AsTime().UnixNano())\n\trequire.Equal(t, expected.StatusCode, actual.Status().Code().String())\n\trequire.Equal(t, expected.StatusMessage, actual.Status().Message())\n\trequire.Equal(t, time.Duration(expected.Duration), actual.EndTimestamp().AsTime().Sub(actual.StartTimestamp().AsTime()))\n\n\trequireBoolAttrs(t, expected.Attributes.BoolKeys, expected.Attributes.BoolValues, actual.Attributes())\n\trequireDoubleAttrs(t, expected.Attributes.DoubleKeys, expected.Attributes.DoubleValues, actual.Attributes())\n\trequireIntAttrs(t, expected.Attributes.IntKeys, expected.Attributes.IntValues, actual.Attributes())\n\trequireStrAttrs(t, expected.Attributes.StrKeys, expected.Attributes.StrValues, actual.Attributes())\n\trequireComplexAttrs(t, expected.Attributes.ComplexKeys, expected.Attributes.ComplexValues, actual.Attributes())\n\n\trequire.Len(t, expected.EventNames, actual.Events().Len())\n\tfor i, e := range actual.Events().All() {\n\t\trequire.Equal(t, expected.EventNames[i], e.Name())\n\t\trequire.Equal(t, expected.EventTimestamps[i].UnixNano(), e.Timestamp().AsTime().UnixNano())\n\n\t\trequireBoolAttrs(t, expected.EventAttributes.BoolKeys[i], expected.EventAttributes.BoolValues[i], e.Attributes())\n\t\trequireDoubleAttrs(t, expected.EventAttributes.DoubleKeys[i], expected.EventAttributes.DoubleValues[i], e.Attributes())\n\t\trequireIntAttrs(t, expected.EventAttributes.IntKeys[i], expected.EventAttributes.IntValues[i], e.Attributes())\n\t\trequireStrAttrs(t, expected.EventAttributes.StrKeys[i], expected.EventAttributes.StrValues[i], e.Attributes())\n\t\trequireComplexAttrs(t, expected.EventAttributes.ComplexKeys[i], expected.EventAttributes.ComplexValues[i], e.Attributes())\n\t}\n\n\trequire.Len(t, expected.LinkSpanIDs, actual.Links().Len())\n\tfor i, l := range actual.Links().All() {\n\t\trequire.Equal(t, expected.LinkTraceIDs[i], l.TraceID().String())\n\t\trequire.Equal(t, expected.LinkSpanIDs[i], l.SpanID().String())\n\t\trequire.Equal(t, expected.LinkTraceStates[i], l.TraceState().AsRaw())\n\t}\n}\n\nfunc requireBoolAttrs(t *testing.T, expectedKeys []string, expectedVals []bool, attrs pcommon.Map) {\n\tfor i, k := range expectedKeys {\n\t\tval, ok := attrs.Get(k)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, expectedVals[i], val.Bool())\n\t}\n}\n\nfunc requireDoubleAttrs(t *testing.T, expectedKeys []string, expectedVals []float64, attrs pcommon.Map) {\n\tfor i, k := range expectedKeys {\n\t\tval, ok := attrs.Get(k)\n\t\trequire.True(t, ok)\n\t\trequire.InEpsilon(t, expectedVals[i], val.Double(), 1e-9)\n\t}\n}\n\nfunc requireIntAttrs(t *testing.T, expectedKeys []string, expectedVals []int64, attrs pcommon.Map) {\n\tfor i, k := range expectedKeys {\n\t\tval, ok := attrs.Get(k)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, expectedVals[i], val.Int())\n\t}\n}\n\nfunc requireStrAttrs(t *testing.T, expectedKeys []string, expectedVals []string, attrs pcommon.Map) {\n\tfor i, k := range expectedKeys {\n\t\tval, ok := attrs.Get(k)\n\t\trequire.True(t, ok)\n\t\trequire.Equal(t, expectedVals[i], val.Str())\n\t}\n}\n\nfunc requireComplexAttrs(t *testing.T, expectedKeys []string, expectedVals []string, attrs pcommon.Map) {\n\tfor i, k := range expectedKeys {\n\t\tswitch {\n\t\tcase strings.HasPrefix(k, \"@bytes@\"):\n\t\t\tkey := strings.TrimPrefix(expectedKeys[i], \"@bytes@\")\n\t\t\tval, ok := attrs.Get(key)\n\t\t\trequire.True(t, ok)\n\t\t\tdecoded, err := base64.StdEncoding.DecodeString(expectedVals[i])\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, decoded, val.Bytes().AsRaw())\n\t\tcase strings.HasPrefix(k, \"@map@\"):\n\t\t\tkey := strings.TrimPrefix(expectedKeys[i], \"@map@\")\n\t\t\tval, ok := attrs.Get(key)\n\t\t\trequire.True(t, ok)\n\n\t\t\tm := &xpdata.JSONUnmarshaler{}\n\t\t\texpectedVal, err := m.UnmarshalValue([]byte(expectedVals[i]))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.True(t, expectedVal.Map().Equal(val.Map()))\n\t\tcase strings.HasPrefix(k, \"@slice@\"):\n\t\t\tkey := strings.TrimPrefix(expectedKeys[i], \"@slice@\")\n\t\t\tval, ok := attrs.Get(key)\n\t\t\trequire.True(t, ok)\n\n\t\t\tm := &xpdata.JSONUnmarshaler{}\n\t\t\texpectedVal, err := m.UnmarshalValue([]byte(expectedVals[i]))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.True(t, expectedVal.Slice().Equal(val.Slice()))\n\t\tdefault:\n\t\t\tt.Fatalf(\"unsupported complex attribute key: %s\", k)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/attribute_metadata.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\n// attrTypes holds the value types for an attribute key at different levels.\n// These types are populated by the materialized views defined in\n// internal/storage/v2/clickhouse/sql/create_attribute_metadata_mv.sql,\n// internal/storage/v2/clickhouse/sql/create_event_attribute_metadata_mv.sql, and\n// internal/storage/v2/clickhouse/sql/create_link_attribute_metadata_mv.sql\ntype attrTypes struct {\n\tresource []pcommon.ValueType\n\tscope    []pcommon.ValueType\n\tspan     []pcommon.ValueType\n\tevent    []pcommon.ValueType\n\tlink     []pcommon.ValueType\n}\n\n// attributeMetadata maps attribute keys to their types per level.\n// Example: attributeMetadata[\"http.status\"].span = [\"int\", \"str\"]\ntype attributeMetadata map[string]attrTypes\n\n// getAttributeMetadata retrieves the types stored in ClickHouse for attributes that arrive as strings.\n//\n// Query Flow:\n// 1. HTTP/gRPC API receives tag filters as query parameters (e.g., ?tag=http.status:200)\n// 2. The query parser parses them into map[string]string\n// 3. The map gets converted to a pcommon.Map using PutStr() for all values\n// 4. This function receives those string-typed attributes and looks up their actual storage types\n//\n// The query APIs (both HTTP and gRPC) only accept string values for tag filters, regardless\n// of how attributes were originally stored in ClickHouse. For example:\n//   - A bool attribute stored as true arrives as the string \"true\"\n//   - An int attribute stored as 123 arrives as the string \"123\"\n//   - A string attribute stored as \"ok\" arrives as the string \"ok\"\n//\n// To query ClickHouse correctly, we need to:\n//  1. Look up the actual type(s) from the attribute_metadata table\n//  2. Convert the string back to the original type for filtering\n//  3. Query the appropriate typed column (bool_attributes, int_attributes, etc.)\n//\n// Since attributes can be stored with different types across different spans\n// (e.g. \"http.status\" could be an int in one span and a string in another),\n// the metadata can return multiple types for a single key. We build OR conditions\n// to match any of the possible types.\n//\n// Only string-typed attributes from pcommon.Map are looked up since those are the ones\n// that originated from the query API's string-only input format.\nfunc (r *Reader) getAttributeMetadata(ctx context.Context, attributes pcommon.Map) (attributeMetadata, error) {\n\tquery, args := buildSelectAttributeMetadataQuery(attributes)\n\tmetadata := make(attributeMetadata)\n\tif len(args) == 0 {\n\t\t// No string attributes to look up\n\t\treturn metadata, nil\n\t}\n\n\trows, err := r.conn.Query(ctx, query, args...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query attribute metadata: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar attrMeta dbmodel.AttributeMetadata\n\t\tif err := rows.ScanStruct(&attrMeta); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to scan row: %w\", err)\n\t\t}\n\n\t\tlevels := metadata[attrMeta.AttributeKey]\n\t\tswitch attrMeta.Level {\n\t\tcase \"resource\":\n\t\t\tlevels.resource = append(levels.resource, jptrace.StringToValueType(attrMeta.Type))\n\t\tcase \"scope\":\n\t\t\tlevels.scope = append(levels.scope, jptrace.StringToValueType(attrMeta.Type))\n\t\tcase \"span\":\n\t\t\tlevels.span = append(levels.span, jptrace.StringToValueType(attrMeta.Type))\n\t\tcase \"event\":\n\t\t\tlevels.event = append(levels.event, jptrace.StringToValueType(attrMeta.Type))\n\t\tcase \"link\":\n\t\t\tlevels.link = append(levels.link, jptrace.StringToValueType(attrMeta.Type))\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown attribute level %q\", attrMeta.Level)\n\t\t}\n\t\tmetadata[attrMeta.AttributeKey] = levels\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"error iterating attribute metadata rows: %w\", err)\n\t}\n\treturn metadata, nil\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/attribute_metadata_test.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\nfunc TestGetAttributeMetadata_ErrorCases(t *testing.T) {\n\tattrs := pcommon.NewMap()\n\tattrs.PutStr(\"http.method\", \"GET\")\n\n\ttests := []struct {\n\t\tname        string\n\t\tdriver      *testDriver\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"QueryError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\t\t\trows: nil,\n\t\t\t\t\t\terr:  assert.AnError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to query attribute metadata\",\n\t\t},\n\t\t{\n\t\t\tname: \"ScanStructError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\t\t\trows: &testRows[dbmodel.AttributeMetadata]{\n\t\t\t\t\t\t\tdata: []dbmodel.AttributeMetadata{{\n\t\t\t\t\t\t\t\tAttributeKey: \"http.method\",\n\t\t\t\t\t\t\t\tType:         \"str\",\n\t\t\t\t\t\t\t\tLevel:        \"span\",\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\tscanErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to scan row\",\n\t\t},\n\t\t{\n\t\t\tname: \"RowsIterationError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\t\t\trows: &testRows[dbmodel.AttributeMetadata]{\n\t\t\t\t\t\t\tdata: []dbmodel.AttributeMetadata{{\n\t\t\t\t\t\t\t\tAttributeKey: \"http.method\",\n\t\t\t\t\t\t\t\tType:         \"str\",\n\t\t\t\t\t\t\t\tLevel:        \"span\",\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\tscanFn: func(dest any, src dbmodel.AttributeMetadata) error {\n\t\t\t\t\t\t\t\tptr, ok := dest.(*dbmodel.AttributeMetadata)\n\t\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\t\treturn assert.AnError\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t*ptr = src\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\trowsErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"error iterating attribute metadata rows\",\n\t\t},\n\t\t{\n\t\t\tname: \"UnknownLevelError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\t\t\trows: &testRows[dbmodel.AttributeMetadata]{\n\t\t\t\t\t\t\tdata: []dbmodel.AttributeMetadata{{\n\t\t\t\t\t\t\t\tAttributeKey: \"http.method\",\n\t\t\t\t\t\t\t\tType:         \"str\",\n\t\t\t\t\t\t\t\tLevel:        \"unknown\",\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\tscanFn: func(dest any, src dbmodel.AttributeMetadata) error {\n\t\t\t\t\t\t\t\tptr, ok := dest.(*dbmodel.AttributeMetadata)\n\t\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\t\treturn assert.AnError\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t*ptr = src\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"unknown attribute level\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treader := NewReader(tt.driver, ReaderConfig{})\n\t\t\t_, err := reader.getAttributeMetadata(t.Context(), attrs)\n\t\t\trequire.Error(t, err)\n\t\t\tassert.ErrorContains(t, err, tt.expectedErr)\n\t\t})\n\t}\n}\n\nfunc TestGetAttributeMetadata_NoStringAttributes(t *testing.T) {\n\tattrs := pcommon.NewMap()\n\tattrs.PutBool(\"some.bool\", true)\n\tattrs.PutInt(\"some.int\", 42)\n\tattrs.PutDouble(\"some.double\", 3.14)\n\n\tdriver := &testDriver{\n\t\tt: t,\n\t}\n\n\treader := NewReader(driver, ReaderConfig{})\n\tmetadata, err := reader.getAttributeMetadata(t.Context(), attrs)\n\trequire.NoError(t, err)\n\tassert.Empty(t, metadata)\n\tassert.Empty(t, driver.recordedQueries)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/attribute_metadata.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// AttributeMetadata represents metadata about an attribute stored in ClickHouse.\n// This is populated by the attribute_metadata materialized view which tracks\n// all unique (attribute_key, type, level) tuples observed in the spans table.\n//\n// The same attribute key can have multiple entries with different types or levels.\n// For example, \"http.status\" might appear as both type=\"int\" and type=\"str\" if\n// different spans store it with different types.\ntype AttributeMetadata struct {\n\t// AttributeKey is the name of the attribute (e.g., \"http.status\", \"service.name\")\n\tAttributeKey string `ch:\"attribute_key\"`\n\t// Type is the data type of the attribute value.\n\t// One of: \"bool\", \"double\", \"int\", \"str\", \"bytes\", \"map\", \"slice\"\n\tType string `ch:\"type\"`\n\t// Level is the scope level where this attribute appears.\n\t// One of: \"span\", \"resource\", \"scope\"\n\tLevel string `ch:\"level\"`\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/dbmodel_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"encoding/base64\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/xpdata\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nfunc TestRoundTrip(t *testing.T) {\n\tnow := time.Now().UTC()\n\tduration := 2 * time.Second\n\n\tt.Run(\"ToRow->FromRow\", func(t *testing.T) {\n\t\trs := createTestResource()\n\t\tsc := createTestScope()\n\t\tspan := createTestSpan(now, duration)\n\n\t\texpected := createTestTrace(now, duration)\n\n\t\trow := ToRow(rs, sc, span)\n\t\ttrace := FromRow(row)\n\t\trequire.Equal(t, expected, trace)\n\t})\n\n\tt.Run(\"FromRow->ToRow\", func(t *testing.T) {\n\t\tspanRow := createTestSpanRow(t, now, duration)\n\n\t\ttrace := FromRow(spanRow)\n\t\trs := trace.ResourceSpans().At(0).Resource()\n\t\tsc := trace.ResourceSpans().At(0).ScopeSpans().At(0).Scope()\n\t\tspan := trace.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\n\t\trow := ToRow(rs, sc, span)\n\t\trequire.Equal(t, spanRow, row)\n\t})\n}\n\nfunc createTestTrace(now time.Time, duration time.Duration) ptrace.Traces {\n\trs := createTestResource()\n\tsc := createTestScope()\n\tspan := createTestSpan(now, duration)\n\n\ttd := ptrace.NewTraces()\n\trsSpans := td.ResourceSpans().AppendEmpty()\n\trs.CopyTo(rsSpans.Resource())\n\tscSpans := rsSpans.ScopeSpans().AppendEmpty()\n\tsc.CopyTo(scSpans.Scope())\n\tspan.CopyTo(scSpans.Spans().AppendEmpty())\n\treturn td\n}\n\nfunc createTestResource() pcommon.Resource {\n\trs := pcommon.NewResource()\n\trs.Attributes().PutStr(otelsemconv.ServiceNameKey, \"test-service\")\n\taddTestAttributes(rs.Attributes())\n\treturn rs\n}\n\nfunc createTestScope() pcommon.InstrumentationScope {\n\tsc := pcommon.NewInstrumentationScope()\n\tsc.SetName(\"test-scope\")\n\tsc.SetVersion(\"v1.0.0\")\n\taddTestAttributes(sc.Attributes())\n\treturn sc\n}\n\nfunc createTestSpan(now time.Time, duration time.Duration) ptrace.Span {\n\tspan := ptrace.NewSpan()\n\tspan.SetSpanID(pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}))\n\tspan.SetTraceID(pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}))\n\tspan.TraceState().FromRaw(\"state1\")\n\tspan.SetParentSpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 2})\n\tspan.SetName(\"test-span\")\n\tspan.SetKind(ptrace.SpanKindServer)\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(now))\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(now.Add(duration)))\n\tspan.Status().SetCode(ptrace.StatusCodeOk)\n\tspan.Status().SetMessage(\"test-status-message\")\n\n\taddTestAttributes(span.Attributes())\n\taddSpanEvent(span, now)\n\taddSpanLink(span)\n\n\treturn span\n}\n\nfunc addSpanEvent(span ptrace.Span, now time.Time) {\n\tevent := span.Events().AppendEmpty()\n\tevent.SetName(\"test-event\")\n\tevent.SetTimestamp(pcommon.NewTimestampFromTime(now))\n\taddTestAttributes(event.Attributes())\n}\n\nfunc addSpanLink(span ptrace.Span) {\n\tlink := span.Links().AppendEmpty()\n\tlink.SetTraceID(pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}))\n\tlink.SetSpanID(pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 4}))\n\tlink.TraceState().FromRaw(\"link-state\")\n\taddTestAttributes(link.Attributes())\n}\n\nfunc addTestAttributes(attrs pcommon.Map) {\n\tattrs.PutBool(\"bool_attr\", true)\n\tattrs.PutDouble(\"double_attr\", 3.14)\n\tattrs.PutInt(\"int_attr\", 42)\n\tattrs.PutStr(\"string_attr\", \"string_value\")\n\tattrs.PutEmptyBytes(\"bytes_attr\").FromRaw([]byte(\"bytes_value\"))\n\tattrs.PutEmptyMap(\"map_attr\").FromRaw(map[string]any{\"key\": \"value\"})\n\tattrs.PutEmptySlice(\"slice_attr\").FromRaw([]any{1, 2, 3})\n}\n\nfunc createTestSpanRow(t *testing.T, now time.Time, duration time.Duration) *SpanRow {\n\tt.Helper()\n\tencodedBytes := base64.StdEncoding.EncodeToString([]byte(\"bytes_value\"))\n\n\tvm := pcommon.NewValueMap()\n\tvm.Map().PutStr(\"key\", \"value\")\n\tm := &xpdata.JSONMarshaler{}\n\tvmJSON, err := m.MarshalValue(vm)\n\trequire.NoError(t, err)\n\n\tvs := pcommon.NewValueSlice()\n\tvs.Slice().AppendEmpty().SetInt(1)\n\tvs.Slice().AppendEmpty().SetInt(2)\n\tvs.Slice().AppendEmpty().SetInt(3)\n\tvsJSON, err := m.MarshalValue(vs)\n\trequire.NoError(t, err)\n\n\treturn &SpanRow{\n\t\tID:            \"0000000000000001\",\n\t\tTraceID:       \"00000000000000000000000000000001\",\n\t\tTraceState:    \"state1\",\n\t\tParentSpanID:  \"0000000000000002\",\n\t\tName:          \"test-span\",\n\t\tKind:          \"server\",\n\t\tStartTime:     now,\n\t\tStatusCode:    \"Ok\",\n\t\tStatusMessage: \"test-status-message\",\n\t\tDuration:      duration.Nanoseconds(),\n\t\tAttributes: Attributes{\n\t\t\tBoolKeys:      []string{\"bool_attr\"},\n\t\t\tBoolValues:    []bool{true},\n\t\t\tDoubleKeys:    []string{\"double_attr\"},\n\t\t\tDoubleValues:  []float64{3.14},\n\t\t\tIntKeys:       []string{\"int_attr\"},\n\t\t\tIntValues:     []int64{42},\n\t\t\tStrKeys:       []string{\"string_attr\"},\n\t\t\tStrValues:     []string{\"string_value\"},\n\t\t\tComplexKeys:   []string{\"@bytes@bytes_attr\", \"@map@map_attr\", \"@slice@slice_attr\"},\n\t\t\tComplexValues: []string{encodedBytes, string(vmJSON), string(vsJSON)},\n\t\t},\n\t\tEventNames:      []string{\"test-event\"},\n\t\tEventTimestamps: []time.Time{now},\n\t\tEventAttributes: Attributes2D{\n\t\t\tBoolKeys:      [][]string{{\"bool_attr\"}},\n\t\t\tBoolValues:    [][]bool{{true}},\n\t\t\tDoubleKeys:    [][]string{{\"double_attr\"}},\n\t\t\tDoubleValues:  [][]float64{{3.14}},\n\t\t\tIntKeys:       [][]string{{\"int_attr\"}},\n\t\t\tIntValues:     [][]int64{{42}},\n\t\t\tStrKeys:       [][]string{{\"string_attr\"}},\n\t\t\tStrValues:     [][]string{{\"string_value\"}},\n\t\t\tComplexKeys:   [][]string{{\"@bytes@bytes_attr\", \"@map@map_attr\", \"@slice@slice_attr\"}},\n\t\t\tComplexValues: [][]string{{encodedBytes, string(vmJSON), string(vsJSON)}},\n\t\t},\n\t\tLinkTraceIDs:    []string{\"00000000000000000000000000000003\"},\n\t\tLinkSpanIDs:     []string{\"0000000000000004\"},\n\t\tLinkTraceStates: []string{\"link-state\"},\n\t\tLinkAttributes: Attributes2D{\n\t\t\tBoolKeys:      [][]string{{\"bool_attr\"}},\n\t\t\tBoolValues:    [][]bool{{true}},\n\t\t\tDoubleKeys:    [][]string{{\"double_attr\"}},\n\t\t\tDoubleValues:  [][]float64{{3.14}},\n\t\t\tIntKeys:       [][]string{{\"int_attr\"}},\n\t\t\tIntValues:     [][]int64{{42}},\n\t\t\tStrKeys:       [][]string{{\"string_attr\"}},\n\t\t\tStrValues:     [][]string{{\"string_value\"}},\n\t\t\tComplexKeys:   [][]string{{\"@bytes@bytes_attr\", \"@map@map_attr\", \"@slice@slice_attr\"}},\n\t\t\tComplexValues: [][]string{{encodedBytes, string(vmJSON), string(vsJSON)}},\n\t\t},\n\t\tServiceName: \"test-service\",\n\t\tResourceAttributes: Attributes{\n\t\t\tBoolKeys:      []string{\"bool_attr\"},\n\t\t\tBoolValues:    []bool{true},\n\t\t\tDoubleKeys:    []string{\"double_attr\"},\n\t\t\tDoubleValues:  []float64{3.14},\n\t\t\tIntKeys:       []string{\"int_attr\"},\n\t\t\tIntValues:     []int64{42},\n\t\t\tStrKeys:       []string{\"service.name\", \"string_attr\"},\n\t\t\tStrValues:     []string{\"test-service\", \"string_value\"},\n\t\t\tComplexKeys:   []string{\"@bytes@bytes_attr\", \"@map@map_attr\", \"@slice@slice_attr\"},\n\t\t\tComplexValues: []string{encodedBytes, string(vmJSON), string(vsJSON)},\n\t\t},\n\t\tScopeName:    \"test-scope\",\n\t\tScopeVersion: \"v1.0.0\",\n\t\tScopeAttributes: Attributes{\n\t\t\tBoolKeys:      []string{\"bool_attr\"},\n\t\t\tBoolValues:    []bool{true},\n\t\t\tDoubleKeys:    []string{\"double_attr\"},\n\t\t\tDoubleValues:  []float64{3.14},\n\t\t\tIntKeys:       []string{\"int_attr\"},\n\t\t\tIntValues:     []int64{42},\n\t\t\tStrKeys:       []string{\"string_attr\"},\n\t\t\tStrValues:     []string{\"string_value\"},\n\t\t\tComplexKeys:   []string{\"@bytes@bytes_attr\", \"@map@map_attr\", \"@slice@slice_attr\"},\n\t\t\tComplexValues: []string{encodedBytes, string(vmJSON), string(vsJSON)},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/from.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/xpdata\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\n// FromRow converts a ClickHouse stored span row to an OpenTelemetry Traces object.\nfunc FromRow(storedSpan *SpanRow) ptrace.Traces {\n\ttrace := ptrace.NewTraces()\n\tresourceSpans := trace.ResourceSpans().AppendEmpty()\n\tscopeSpans := resourceSpans.ScopeSpans().AppendEmpty()\n\tspan := scopeSpans.Spans().AppendEmpty()\n\n\tsp, err := convertSpan(storedSpan)\n\tsp.CopyTo(span)\n\tif err != nil {\n\t\tjptrace.AddWarnings(span, err.Error())\n\t}\n\n\tresource := resourceSpans.Resource()\n\trs := convertResource(storedSpan, span)\n\trs.CopyTo(resource)\n\n\tscope := scopeSpans.Scope()\n\tsc := convertScope(storedSpan, span)\n\tsc.CopyTo(scope)\n\n\treturn trace\n}\n\nfunc convertResource(sr *SpanRow, spanForWarnings ptrace.Span) pcommon.Resource {\n\tresource := ptrace.NewResourceSpans().Resource()\n\tresource.Attributes().PutStr(otelsemconv.ServiceNameKey, sr.ServiceName)\n\tputAttributes(\n\t\tresource.Attributes(),\n\t\t&sr.ResourceAttributes,\n\t\tspanForWarnings,\n\t)\n\treturn resource\n}\n\nfunc convertScope(sr *SpanRow, spanForWarnings ptrace.Span) pcommon.InstrumentationScope {\n\tscope := ptrace.NewScopeSpans().Scope()\n\tscope.SetName(sr.ScopeName)\n\tscope.SetVersion(sr.ScopeVersion)\n\tputAttributes(\n\t\tscope.Attributes(),\n\t\t&sr.ScopeAttributes,\n\t\tspanForWarnings,\n\t)\n\n\treturn scope\n}\n\nfunc convertSpan(sr *SpanRow) (ptrace.Span, error) {\n\tspan := ptrace.NewSpan()\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(sr.StartTime))\n\ttraceId, err := hex.DecodeString(sr.TraceID)\n\tif err != nil {\n\t\treturn span, fmt.Errorf(\"failed to decode trace ID: %w\", err)\n\t}\n\tspan.SetTraceID(pcommon.TraceID(traceId))\n\tspanId, err := hex.DecodeString(sr.ID)\n\tif err != nil {\n\t\treturn span, fmt.Errorf(\"failed to decode span ID: %w\", err)\n\t}\n\tspan.SetSpanID(pcommon.SpanID(spanId))\n\tparentSpanId, err := hex.DecodeString(sr.ParentSpanID)\n\tif err != nil {\n\t\treturn span, fmt.Errorf(\"failed to decode parent span ID: %w\", err)\n\t}\n\tif len(parentSpanId) != 0 {\n\t\tspan.SetParentSpanID(pcommon.SpanID(parentSpanId))\n\t}\n\tspan.TraceState().FromRaw(sr.TraceState)\n\tspan.SetName(sr.Name)\n\tspan.SetKind(jptrace.StringToSpanKind(sr.Kind))\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(sr.StartTime.Add(time.Duration(sr.Duration))))\n\tspan.Status().SetCode(jptrace.StringToStatusCode(sr.StatusCode))\n\tspan.Status().SetMessage(sr.StatusMessage)\n\n\tputAttributes(\n\t\tspan.Attributes(),\n\t\t&sr.Attributes,\n\t\tspan,\n\t)\n\n\tfor i, e := range sr.EventNames {\n\t\tevent := span.Events().AppendEmpty()\n\t\tevent.SetName(e)\n\t\tevent.SetTimestamp(pcommon.NewTimestampFromTime(sr.EventTimestamps[i]))\n\t\tputAttributes2D(event.Attributes(), &sr.EventAttributes, i, span)\n\t}\n\n\tfor i, l := range sr.LinkTraceIDs {\n\t\tlink := span.Links().AppendEmpty()\n\t\ttraceID, err := hex.DecodeString(l)\n\t\tif err != nil {\n\t\t\tjptrace.AddWarnings(span, fmt.Sprintf(\"failed to decode link trace ID: %v\", err))\n\t\t\tcontinue\n\t\t}\n\t\tlink.SetTraceID(pcommon.TraceID(traceID))\n\t\tspanID, err := hex.DecodeString(sr.LinkSpanIDs[i])\n\t\tif err != nil {\n\t\t\tjptrace.AddWarnings(span, fmt.Sprintf(\"failed to decode link span ID: %v\", err))\n\t\t\tcontinue\n\t\t}\n\t\tlink.SetSpanID(pcommon.SpanID(spanID))\n\t\tlink.TraceState().FromRaw(sr.LinkTraceStates[i])\n\n\t\tputAttributes2D(link.Attributes(), &sr.LinkAttributes, i, span)\n\t}\n\n\treturn span, nil\n}\n\nfunc putAttributes2D(\n\tattrs pcommon.Map,\n\tstoredAttrs *Attributes2D,\n\tidx int,\n\tspanForWarnings ptrace.Span,\n) {\n\tputAttributes(\n\t\tattrs,\n\t\t&Attributes{\n\t\t\tBoolKeys:      storedAttrs.BoolKeys[idx],\n\t\t\tBoolValues:    storedAttrs.BoolValues[idx],\n\t\t\tDoubleKeys:    storedAttrs.DoubleKeys[idx],\n\t\t\tDoubleValues:  storedAttrs.DoubleValues[idx],\n\t\t\tIntKeys:       storedAttrs.IntKeys[idx],\n\t\t\tIntValues:     storedAttrs.IntValues[idx],\n\t\t\tStrKeys:       storedAttrs.StrKeys[idx],\n\t\t\tStrValues:     storedAttrs.StrValues[idx],\n\t\t\tComplexKeys:   storedAttrs.ComplexKeys[idx],\n\t\t\tComplexValues: storedAttrs.ComplexValues[idx],\n\t\t},\n\t\tspanForWarnings,\n\t)\n}\n\nfunc putAttributes(\n\tattrs pcommon.Map,\n\tstoredAttrs *Attributes,\n\tspanForWarnings ptrace.Span,\n) {\n\tfor i := 0; i < len(storedAttrs.BoolKeys); i++ {\n\t\tattrs.PutBool(storedAttrs.BoolKeys[i], storedAttrs.BoolValues[i])\n\t}\n\tfor i := 0; i < len(storedAttrs.DoubleKeys); i++ {\n\t\tattrs.PutDouble(storedAttrs.DoubleKeys[i], storedAttrs.DoubleValues[i])\n\t}\n\tfor i := 0; i < len(storedAttrs.IntKeys); i++ {\n\t\tattrs.PutInt(storedAttrs.IntKeys[i], storedAttrs.IntValues[i])\n\t}\n\tfor i := 0; i < len(storedAttrs.StrKeys); i++ {\n\t\tattrs.PutStr(storedAttrs.StrKeys[i], storedAttrs.StrValues[i])\n\t}\n\tfor i := 0; i < len(storedAttrs.ComplexKeys); i++ {\n\t\tswitch {\n\t\tcase strings.HasPrefix(storedAttrs.ComplexKeys[i], \"@bytes@\"):\n\t\t\tdecoded, err := base64.StdEncoding.DecodeString(storedAttrs.ComplexValues[i])\n\t\t\tif err != nil {\n\t\t\t\tjptrace.AddWarnings(spanForWarnings, fmt.Sprintf(\"failed to decode bytes attribute %q: %s\", storedAttrs.ComplexKeys[i], err.Error()))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk := strings.TrimPrefix(storedAttrs.ComplexKeys[i], \"@bytes@\")\n\t\t\tattrs.PutEmptyBytes(k).FromRaw(decoded)\n\t\tcase strings.HasPrefix(storedAttrs.ComplexKeys[i], \"@slice@\"):\n\t\t\tk := strings.TrimPrefix(storedAttrs.ComplexKeys[i], \"@slice@\")\n\t\t\tm := &xpdata.JSONUnmarshaler{}\n\t\t\tval, err := m.UnmarshalValue([]byte(storedAttrs.ComplexValues[i]))\n\t\t\tif err != nil {\n\t\t\t\tjptrace.AddWarnings(\n\t\t\t\t\tspanForWarnings,\n\t\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\t\"failed to unmarshal slice attribute %q: %s\",\n\t\t\t\t\t\tstoredAttrs.ComplexKeys[i],\n\t\t\t\t\t\terr.Error(),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tattrs.PutEmptySlice(k).FromRaw(val.Slice().AsRaw())\n\t\tcase strings.HasPrefix(storedAttrs.ComplexKeys[i], \"@map@\"):\n\t\t\tk := strings.TrimPrefix(storedAttrs.ComplexKeys[i], \"@map@\")\n\t\t\tm := &xpdata.JSONUnmarshaler{}\n\t\t\tval, err := m.UnmarshalValue([]byte(storedAttrs.ComplexValues[i]))\n\t\t\tif err != nil {\n\t\t\t\tjptrace.AddWarnings(\n\t\t\t\t\tspanForWarnings,\n\t\t\t\t\tfmt.Sprintf(\"failed to unmarshal map attribute %q: %s\",\n\t\t\t\t\t\tstoredAttrs.ComplexKeys[i],\n\t\t\t\t\t\terr.Error(),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tattrs.PutEmptyMap(k).FromRaw(val.Map().AsRaw())\n\t\tdefault:\n\t\t\tjptrace.AddWarnings(\n\t\t\t\tspanForWarnings,\n\t\t\t\tfmt.Sprintf(\"unsupported complex attribute key: %q\", storedAttrs.ComplexKeys[i]),\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/from_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n)\n\nfunc TestFromRow(t *testing.T) {\n\tnow := time.Now().UTC()\n\tduration := 2 * time.Second\n\n\tspanRow := createTestSpanRow(t, now, duration)\n\n\texpected := createTestTrace(now, duration)\n\n\trow := FromRow(spanRow)\n\trequire.Equal(t, expected, row)\n}\n\nfunc TestFromRow_DecodeID(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targ  *SpanRow\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"decode span trace id failed\",\n\t\t\targ: &SpanRow{\n\t\t\t\tTraceID: \"0x\",\n\t\t\t},\n\t\t\twant: \"failed to decode trace ID: encoding/hex: invalid byte: U+0078 'x'\",\n\t\t},\n\t\t{\n\t\t\tname: \"decode span id failed\",\n\t\t\targ: &SpanRow{\n\t\t\t\tTraceID: \"00010001000100010001000100010001\",\n\t\t\t\tID:      \"0x\",\n\t\t\t},\n\t\t\twant: \"failed to decode span ID: encoding/hex: invalid byte: U+0078 'x'\",\n\t\t},\n\t\t{\n\t\t\tname: \"decode span parent id failed\",\n\t\t\targ: &SpanRow{\n\t\t\t\tTraceID:      \"00010001000100010001000100010001\",\n\t\t\t\tID:           \"0001000100010001\",\n\t\t\t\tParentSpanID: \"0x\",\n\t\t\t},\n\t\t\twant: \"failed to decode parent span ID: encoding/hex: invalid byte: U+0078 'x'\",\n\t\t},\n\t\t{\n\t\t\tname: \"decode link trace id failed\",\n\t\t\targ: &SpanRow{\n\t\t\t\tTraceID:      \"00010001000100010001000100010001\",\n\t\t\t\tID:           \"0001000100010001\",\n\t\t\t\tParentSpanID: \"0001000100010001\",\n\t\t\t\tLinkTraceIDs: []string{\"0x\"},\n\t\t\t},\n\t\t\twant: \"failed to decode link trace ID: encoding/hex: invalid byte: U+0078 'x'\",\n\t\t},\n\t\t{\n\t\t\tname: \"decode link span id failed\",\n\t\t\targ: &SpanRow{\n\t\t\t\tTraceID:      \"00010001000100010001000100010001\",\n\t\t\t\tID:           \"0001000100010001\",\n\t\t\t\tParentSpanID: \"0001000100010001\",\n\t\t\t\tLinkTraceIDs: []string{\"00010001000100010001000100010001\"},\n\t\t\t\tLinkSpanIDs:  []string{\"0x\"},\n\t\t\t},\n\t\t\twant: \"failed to decode link span ID: encoding/hex: invalid byte: U+0078 'x'\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttrace := FromRow(tt.arg)\n\t\t\tspan := trace.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\t\t\trequire.Contains(t, jptrace.GetWarnings(span), tt.want)\n\t\t})\n\t}\n}\n\nfunc TestPutAttributes_Warnings(t *testing.T) {\n\ttests := []struct {\n\t\tname                 string\n\t\tcomplexKeys          []string\n\t\tcomplexValues        []string\n\t\texpectedWarnContains string\n\t}{\n\t\t{\n\t\t\tname:                 \"bytes attribute with invalid base64\",\n\t\t\tcomplexKeys:          []string{\"@bytes@bytes-key\"},\n\t\t\tcomplexValues:        []string{\"invalid-base64\"},\n\t\t\texpectedWarnContains: \"failed to decode bytes attribute \\\"@bytes@bytes-key\\\"\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"failed to unmarshal slice attribute\",\n\t\t\tcomplexKeys:          []string{\"@slice@slice-key\"},\n\t\t\tcomplexValues:        []string{\"notjson\"},\n\t\t\texpectedWarnContains: \"failed to unmarshal slice attribute \\\"@slice@slice-key\\\"\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"failed to unmarshal map attribute\",\n\t\t\tcomplexKeys:          []string{\"@map@map-key\"},\n\t\t\tcomplexValues:        []string{\"notjson\"},\n\t\t\texpectedWarnContains: \"failed to unmarshal map attribute \\\"@map@map-key\\\"\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"unsupported complex attribute key\",\n\t\t\tcomplexKeys:          []string{\"unsupported\"},\n\t\t\tcomplexValues:        []string{\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\"},\n\t\t\texpectedWarnContains: \"unsupported complex attribute key: \\\"unsupported\\\"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tspan := ptrace.NewSpan()\n\t\t\tattributes := pcommon.NewMap()\n\n\t\t\tputAttributes(\n\t\t\t\tattributes,\n\t\t\t\t&Attributes{\n\t\t\t\t\tComplexKeys:   tt.complexKeys,\n\t\t\t\t\tComplexValues: tt.complexValues,\n\t\t\t\t},\n\t\t\t\tspan,\n\t\t\t)\n\n\t\t\twarnings := jptrace.GetWarnings(span)\n\t\t\trequire.Len(t, warnings, 1)\n\t\t\trequire.Contains(t, warnings[0], tt.expectedWarnContains)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/operation.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// Operation represents a single row in the ClickHouse `operations` table.\ntype Operation struct {\n\tName string `ch:\"name\"`\n\t// SpanKind holds the string representation of the span kind from ptrace.SpanKind\n\t// in lowercase.\n\tSpanKind string `ch:\"span_kind\"`\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/service.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\n// Service represents a single row in the ClickHouse `services` table.\ntype Service struct {\n\tName string `ch:\"name\"`\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/spanrow.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"time\"\n\n\t\"github.com/ClickHouse/clickhouse-go/v2/lib/driver\"\n)\n\n// SpanRow represents a single record in the ClickHouse `spans` table.\n//\n// Complex attributes are non-primitive OTLP types that require special serialization\n// before being stored. These types are encoded as follows:\n//\n//   - pcommon.ValueTypeBytes:\n//     Represents raw byte data. The value is Base64-encoded and stored as a string.\n//     Keys for this type are prefixed with `@bytes@`.\n//\n//   - pcommon.ValueTypeSlice:\n//     Represents an OTLP slice (array). The value is serialized to JSON and stored\n//     as a string. Keys for this type are prefixed with `@slice@`.\n//\n//   - pcommon.ValueTypeMap:\n//     Represents an OTLP map. The value is serialized to JSON and stored\n//     as a string. Keys for this type are prefixed with `@map@`.\ntype SpanRow struct {\n\t// --- Span ---\n\tID              string\n\tTraceID         string\n\tTraceState      string\n\tParentSpanID    string\n\tName            string\n\tKind            string\n\tStartTime       time.Time\n\tStatusCode      string\n\tStatusMessage   string\n\tDuration        int64\n\tAttributes      Attributes\n\tEventNames      []string\n\tEventTimestamps []time.Time\n\tEventAttributes Attributes2D\n\tLinkTraceIDs    []string\n\tLinkSpanIDs     []string\n\tLinkTraceStates []string\n\tLinkAttributes  Attributes2D\n\n\t// --- Resource ---\n\tServiceName        string\n\tResourceAttributes Attributes\n\n\t// --- Scope ---\n\tScopeName       string\n\tScopeVersion    string\n\tScopeAttributes Attributes\n}\n\ntype Attributes struct {\n\tBoolKeys      []string\n\tBoolValues    []bool\n\tDoubleKeys    []string\n\tDoubleValues  []float64\n\tIntKeys       []string\n\tIntValues     []int64\n\tStrKeys       []string\n\tStrValues     []string\n\tComplexKeys   []string\n\tComplexValues []string\n}\n\ntype Attributes2D struct {\n\tBoolKeys      [][]string\n\tBoolValues    [][]bool\n\tDoubleKeys    [][]string\n\tDoubleValues  [][]float64\n\tIntKeys       [][]string\n\tIntValues     [][]int64\n\tStrKeys       [][]string\n\tStrValues     [][]string\n\tComplexKeys   [][]string\n\tComplexValues [][]string\n}\n\nfunc ScanRow(rows driver.Rows) (*SpanRow, error) {\n\tvar sr SpanRow\n\terr := rows.Scan(\n\t\t&sr.ID,\n\t\t&sr.TraceID,\n\t\t&sr.TraceState,\n\t\t&sr.ParentSpanID,\n\t\t&sr.Name,\n\t\t&sr.Kind,\n\t\t&sr.StartTime,\n\t\t&sr.StatusCode,\n\t\t&sr.StatusMessage,\n\t\t&sr.Duration,\n\t\t&sr.Attributes.BoolKeys,\n\t\t&sr.Attributes.BoolValues,\n\t\t&sr.Attributes.DoubleKeys,\n\t\t&sr.Attributes.DoubleValues,\n\t\t&sr.Attributes.IntKeys,\n\t\t&sr.Attributes.IntValues,\n\t\t&sr.Attributes.StrKeys,\n\t\t&sr.Attributes.StrValues,\n\t\t&sr.Attributes.ComplexKeys,\n\t\t&sr.Attributes.ComplexValues,\n\t\t&sr.EventNames,\n\t\t&sr.EventTimestamps,\n\t\t&sr.EventAttributes.BoolKeys,\n\t\t&sr.EventAttributes.BoolValues,\n\t\t&sr.EventAttributes.DoubleKeys,\n\t\t&sr.EventAttributes.DoubleValues,\n\t\t&sr.EventAttributes.IntKeys,\n\t\t&sr.EventAttributes.IntValues,\n\t\t&sr.EventAttributes.StrKeys,\n\t\t&sr.EventAttributes.StrValues,\n\t\t&sr.EventAttributes.ComplexKeys,\n\t\t&sr.EventAttributes.ComplexValues,\n\t\t&sr.LinkTraceIDs,\n\t\t&sr.LinkSpanIDs,\n\t\t&sr.LinkTraceStates,\n\t\t&sr.LinkAttributes.BoolKeys,\n\t\t&sr.LinkAttributes.BoolValues,\n\t\t&sr.LinkAttributes.DoubleKeys,\n\t\t&sr.LinkAttributes.DoubleValues,\n\t\t&sr.LinkAttributes.IntKeys,\n\t\t&sr.LinkAttributes.IntValues,\n\t\t&sr.LinkAttributes.StrKeys,\n\t\t&sr.LinkAttributes.StrValues,\n\t\t&sr.LinkAttributes.ComplexKeys,\n\t\t&sr.LinkAttributes.ComplexValues,\n\t\t&sr.ServiceName,\n\t\t&sr.ResourceAttributes.BoolKeys,\n\t\t&sr.ResourceAttributes.BoolValues,\n\t\t&sr.ResourceAttributes.DoubleKeys,\n\t\t&sr.ResourceAttributes.DoubleValues,\n\t\t&sr.ResourceAttributes.IntKeys,\n\t\t&sr.ResourceAttributes.IntValues,\n\t\t&sr.ResourceAttributes.StrKeys,\n\t\t&sr.ResourceAttributes.StrValues,\n\t\t&sr.ResourceAttributes.ComplexKeys,\n\t\t&sr.ResourceAttributes.ComplexValues,\n\t\t&sr.ScopeName,\n\t\t&sr.ScopeVersion,\n\t\t&sr.ScopeAttributes.BoolKeys,\n\t\t&sr.ScopeAttributes.BoolValues,\n\t\t&sr.ScopeAttributes.DoubleKeys,\n\t\t&sr.ScopeAttributes.DoubleValues,\n\t\t&sr.ScopeAttributes.IntKeys,\n\t\t&sr.ScopeAttributes.IntValues,\n\t\t&sr.ScopeAttributes.StrKeys,\n\t\t&sr.ScopeAttributes.StrValues,\n\t\t&sr.ScopeAttributes.ComplexKeys,\n\t\t&sr.ScopeAttributes.ComplexValues,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &sr, nil\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/to.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/xpdata\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\n// ToRow converts an OpenTelemetry Span along with its Resource and Scope to a\n// span row that can be stored in ClickHouse.\nfunc ToRow(\n\tresource pcommon.Resource,\n\tscope pcommon.InstrumentationScope,\n\tspan ptrace.Span,\n) *SpanRow {\n\t// we assume a sanitizer was applied upstream to guarantee non-empty service name\n\tserviceName, _ := resource.Attributes().Get(otelsemconv.ServiceNameKey)\n\tduration := span.EndTimestamp().AsTime().Sub(span.StartTimestamp().AsTime()).Nanoseconds()\n\tsr := &SpanRow{\n\t\tID:            span.SpanID().String(),\n\t\tTraceID:       span.TraceID().String(),\n\t\tTraceState:    span.TraceState().AsRaw(),\n\t\tParentSpanID:  span.ParentSpanID().String(),\n\t\tName:          span.Name(),\n\t\tKind:          jptrace.SpanKindToString(span.Kind()),\n\t\tStartTime:     span.StartTimestamp().AsTime(),\n\t\tStatusCode:    span.Status().Code().String(),\n\t\tStatusMessage: span.Status().Message(),\n\t\tDuration:      duration,\n\t\tServiceName:   serviceName.Str(),\n\t\tScopeName:     scope.Name(),\n\t\tScopeVersion:  scope.Version(),\n\t}\n\tappendAttributes(&sr.Attributes, span.Attributes())\n\tfor _, event := range span.Events().All() {\n\t\tsr.appendEvent(event)\n\t}\n\tfor _, link := range span.Links().All() {\n\t\tsr.appendLink(link)\n\t}\n\tappendAttributes(&sr.ResourceAttributes, resource.Attributes())\n\tappendAttributes(&sr.ScopeAttributes, scope.Attributes())\n\n\treturn sr\n}\n\nfunc appendAttributes(dest *Attributes, attrs pcommon.Map) {\n\ta := extractAttributes(attrs)\n\tdest.BoolKeys = append(dest.BoolKeys, a.BoolKeys...)\n\tdest.BoolValues = append(dest.BoolValues, a.BoolValues...)\n\tdest.DoubleKeys = append(dest.DoubleKeys, a.DoubleKeys...)\n\tdest.DoubleValues = append(dest.DoubleValues, a.DoubleValues...)\n\tdest.IntKeys = append(dest.IntKeys, a.IntKeys...)\n\tdest.IntValues = append(dest.IntValues, a.IntValues...)\n\tdest.StrKeys = append(dest.StrKeys, a.StrKeys...)\n\tdest.StrValues = append(dest.StrValues, a.StrValues...)\n\tdest.ComplexKeys = append(dest.ComplexKeys, a.ComplexKeys...)\n\tdest.ComplexValues = append(dest.ComplexValues, a.ComplexValues...)\n}\n\nfunc appendAttributes2D(dest *Attributes2D, attrs pcommon.Map) {\n\ta := extractAttributes(attrs)\n\tdest.BoolKeys = append(dest.BoolKeys, a.BoolKeys)\n\tdest.BoolValues = append(dest.BoolValues, a.BoolValues)\n\tdest.DoubleKeys = append(dest.DoubleKeys, a.DoubleKeys)\n\tdest.DoubleValues = append(dest.DoubleValues, a.DoubleValues)\n\tdest.IntKeys = append(dest.IntKeys, a.IntKeys)\n\tdest.IntValues = append(dest.IntValues, a.IntValues)\n\tdest.StrKeys = append(dest.StrKeys, a.StrKeys)\n\tdest.StrValues = append(dest.StrValues, a.StrValues)\n\tdest.ComplexKeys = append(dest.ComplexKeys, a.ComplexKeys)\n\tdest.ComplexValues = append(dest.ComplexValues, a.ComplexValues)\n}\n\nfunc (sr *SpanRow) appendEvent(event ptrace.SpanEvent) {\n\tsr.EventNames = append(sr.EventNames, event.Name())\n\tsr.EventTimestamps = append(sr.EventTimestamps, event.Timestamp().AsTime())\n\tappendAttributes2D(&sr.EventAttributes, event.Attributes())\n}\n\nfunc (sr *SpanRow) appendLink(link ptrace.SpanLink) {\n\tsr.LinkTraceIDs = append(sr.LinkTraceIDs, link.TraceID().String())\n\tsr.LinkSpanIDs = append(sr.LinkSpanIDs, link.SpanID().String())\n\tsr.LinkTraceStates = append(sr.LinkTraceStates, link.TraceState().AsRaw())\n\tappendAttributes2D(&sr.LinkAttributes, link.Attributes())\n}\n\nfunc extractAttributes(attrs pcommon.Map) *Attributes {\n\tout := &Attributes{}\n\tattrs.Range(func(k string, v pcommon.Value) bool {\n\t\tswitch v.Type() {\n\t\tcase pcommon.ValueTypeBool:\n\t\t\tout.BoolKeys = append(out.BoolKeys, k)\n\t\t\tout.BoolValues = append(out.BoolValues, v.Bool())\n\t\tcase pcommon.ValueTypeDouble:\n\t\t\tout.DoubleKeys = append(out.DoubleKeys, k)\n\t\t\tout.DoubleValues = append(out.DoubleValues, v.Double())\n\t\tcase pcommon.ValueTypeInt:\n\t\t\tout.IntKeys = append(out.IntKeys, k)\n\t\t\tout.IntValues = append(out.IntValues, v.Int())\n\t\tcase pcommon.ValueTypeStr:\n\t\t\tout.StrKeys = append(out.StrKeys, k)\n\t\t\tout.StrValues = append(out.StrValues, v.Str())\n\t\tcase pcommon.ValueTypeBytes:\n\t\t\tkey := \"@bytes@\" + k\n\t\t\tencoded := base64.StdEncoding.EncodeToString(v.Bytes().AsRaw())\n\t\t\tout.ComplexKeys = append(out.ComplexKeys, key)\n\t\t\tout.ComplexValues = append(out.ComplexValues, encoded)\n\t\tcase pcommon.ValueTypeSlice:\n\t\t\tkey := \"@slice@\" + k\n\t\t\tm := &xpdata.JSONMarshaler{}\n\t\t\tb, err := m.MarshalValue(v)\n\t\t\tif err != nil {\n\t\t\t\tout.StrKeys = append(out.StrKeys, jptrace.WarningsAttribute)\n\t\t\t\tout.StrValues = append(\n\t\t\t\t\tout.StrValues,\n\t\t\t\t\tfmt.Sprintf(\"failed to marshal slice attribute %q: %v\", k, err))\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tout.ComplexKeys = append(out.ComplexKeys, key)\n\t\t\tout.ComplexValues = append(out.ComplexValues, string(b))\n\t\tcase pcommon.ValueTypeMap:\n\t\t\tkey := \"@map@\" + k\n\t\t\tm := &xpdata.JSONMarshaler{}\n\t\t\tb, err := m.MarshalValue(v)\n\t\t\tif err != nil {\n\t\t\t\tout.StrKeys = append(out.StrKeys, jptrace.WarningsAttribute)\n\t\t\t\tout.StrValues = append(\n\t\t\t\t\tout.StrValues,\n\t\t\t\t\tfmt.Sprintf(\"failed to marshal map attribute %q: %v\", k, err))\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tout.ComplexKeys = append(out.ComplexKeys, key)\n\t\t\tout.ComplexValues = append(out.ComplexValues, string(b))\n\t\tdefault:\n\t\t}\n\t\treturn true\n\t})\n\treturn out\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/dbmodel/to_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestToRow(t *testing.T) {\n\tnow := time.Now().UTC()\n\tduration := 2 * time.Second\n\n\trs := createTestResource()\n\tsc := createTestScope()\n\tspan := createTestSpan(now, duration)\n\n\texpected := createTestSpanRow(t, now, duration)\n\n\trow := ToRow(rs, sc, span)\n\trequire.Equal(t, expected, row)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/driver_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/ClickHouse/clickhouse-go/v2/lib/driver\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tsnapshotLocation = \"./snapshots/\"\n)\n\n// Snapshots can be regenerated via:\n//\n// REGENERATE_SNAPSHOTS=true go test -v ./internal/storage/v2/clickhouse/tracestore/...\nvar regenerateSnapshots = os.Getenv(\"REGENERATE_SNAPSHOTS\") == \"true\"\n\n// verifyQuerySnapshot verifies one or more SQL queries against their snapshot files.\n// Queries are indexed sequentially starting from 1, and snapshot files are named as:\n//\n//\tsnapshots/<TestName>_1.sql, snapshots/<TestName>_2.sql, etc.\n//\n// The order of queries passed to this function determines their index and filename.\n// For example, verifyQuerySnapshot(t, query1, query2, query3) will verify against:\n//\n//\tsnapshots/<TestName>_1.sql, snapshots/<TestName>_2.sql, snapshots/<TestName>_3.sql\nfunc verifyQuerySnapshot(t *testing.T, queries ...string) {\n\ttestName := t.Name()\n\tfor i, query := range queries {\n\t\tindex := i + 1\n\t\tsnapshotFile := filepath.Join(snapshotLocation, testName+\"_\"+strconv.Itoa(index)+\".sql\")\n\t\tquery = strings.TrimSpace(query)\n\t\tif regenerateSnapshots {\n\t\t\tdir := filepath.Dir(snapshotFile)\n\t\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create snapshot directory: %v\", err)\n\t\t\t}\n\t\t\tif err := os.WriteFile(snapshotFile, []byte(query+\"\\n\"), 0o644); err != nil {\n\t\t\t\tt.Fatalf(\"failed to write snapshot file: %v\", err)\n\t\t\t}\n\t\t}\n\t\tsnapshot, err := os.ReadFile(snapshotFile)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, strings.TrimSpace(string(snapshot)), query, \"comparing against stored snapshot. Use REGENERATE_SNAPSHOTS=true to rebuild snapshots.\")\n\t}\n}\n\ntype testBatch struct {\n\tdriver.Batch\n\tt          *testing.T\n\tappended   [][]any\n\tappendErr  error\n\tsendCalled bool\n\tsendErr    error\n}\n\nfunc (tb *testBatch) Append(v ...any) error {\n\tif tb.appendErr != nil {\n\t\treturn tb.appendErr\n\t}\n\ttb.appended = append(tb.appended, v)\n\treturn nil\n}\n\nfunc (tb *testBatch) Send() error {\n\tif tb.sendErr != nil {\n\t\treturn tb.sendErr\n\t}\n\ttb.sendCalled = true\n\treturn nil\n}\n\nfunc (*testBatch) Close() error {\n\treturn nil\n}\n\ntype testQueryResponse struct {\n\trows driver.Rows\n\terr  error\n}\n\ntype testBatchResponse struct {\n\tbatch *testBatch\n\terr   error\n}\n\ntype testDriver struct {\n\tdriver.Conn\n\n\tt               *testing.T\n\tqueryResponses  map[string]*testQueryResponse\n\tbatchResponses  map[string]*testBatchResponse\n\trecordedQueries []string\n}\n\nfunc (t *testDriver) Query(_ context.Context, query string, _ ...any) (driver.Rows, error) {\n\tt.recordedQueries = append(t.recordedQueries, query)\n\n\t// Normalize whitespace so substring matching works regardless of indentation.\n\tnormalized := strings.Join(strings.Fields(query), \" \")\n\tfor querySubstring, response := range t.queryResponses {\n\t\tnormalizedQuerySubstring := strings.Join(strings.Fields(querySubstring), \" \")\n\t\tif strings.Contains(normalized, normalizedQuerySubstring) {\n\t\t\treturn response.rows, response.err\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n\ntype testRows[T any] struct {\n\tdriver.Rows\n\n\tdata     []T\n\tindex    int\n\tscanErr  error\n\tscanFn   func(dest any, src T) error\n\tcloseErr error\n\trowsErr  error\n}\n\nfunc (tr *testRows[T]) Close() error {\n\treturn tr.closeErr\n}\n\nfunc (tr *testRows[T]) Err() error {\n\treturn tr.rowsErr\n}\n\nfunc (tr *testRows[T]) Next() bool {\n\treturn tr.index < len(tr.data)\n}\n\nfunc (tr *testRows[T]) ScanStruct(dest any) error {\n\tif tr.scanErr != nil {\n\t\treturn tr.scanErr\n\t}\n\tif tr.index >= len(tr.data) {\n\t\treturn errors.New(\"no more rows\")\n\t}\n\tif tr.scanFn == nil {\n\t\treturn errors.New(\"scanFn is not provided\")\n\t}\n\terr := tr.scanFn(dest, tr.data[tr.index])\n\ttr.index++\n\treturn err\n}\n\nfunc (tr *testRows[T]) Scan(dest ...any) error {\n\tif tr.scanErr != nil {\n\t\treturn tr.scanErr\n\t}\n\tif tr.index >= len(tr.data) {\n\t\treturn errors.New(\"no more rows\")\n\t}\n\tif tr.scanFn == nil {\n\t\treturn errors.New(\"scanFn is not provided\")\n\t}\n\terr := tr.scanFn(dest, tr.data[tr.index])\n\ttr.index++\n\treturn err\n}\n\nfunc (t *testDriver) PrepareBatch(\n\t_ context.Context,\n\tquery string,\n\t_ ...driver.PrepareBatchOption,\n) (driver.Batch, error) {\n\tt.recordedQueries = append(t.recordedQueries, query)\n\n\tfor querySubstring, response := range t.batchResponses {\n\t\tif strings.Contains(query, querySubstring) {\n\t\t\treturn response.batch, response.err\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/query_builder.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/xpdata\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n)\n\n// marshalValueForQuery is a simpler wrapper around xpdata.JSONMarshaler.\n// It can be overridden in tests to simulate marshaling errors.\nvar marshalValueForQuery = func(v pcommon.Value) (string, error) {\n\tm := &xpdata.JSONMarshaler{}\n\tb, err := m.MarshalValue(v)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), nil\n}\n\ntype typedAttributeValue struct {\n\tkey       string\n\tvalue     any\n\tvalueType pcommon.ValueType\n}\n\nfunc appendNewlineAndIndent(q *strings.Builder, indent int) {\n\tq.WriteString(\"\\n\")\n\tfor range indent {\n\t\tq.WriteString(\"\\t\")\n\t}\n}\n\nfunc indentBlock(s string) string {\n\treturn \"\\t\" + strings.ReplaceAll(s, \"\\n\", \"\\n\\t\")\n}\n\nfunc appendAnd(q *strings.Builder, cond string) {\n\tappendNewlineAndIndent(q, 1)\n\tq.WriteString(\"AND \")\n\tq.WriteString(cond)\n}\n\ntype arrayExistsFn func(q *strings.Builder, indent int, prefix string, valueType pcommon.ValueType)\n\nfunc appendArrayExists(q *strings.Builder, indent int, prefix string, valueType pcommon.ValueType) {\n\tstrColumnType := jptrace.ValueTypeToString(valueType)\n\tif valueType == pcommon.ValueTypeBytes || valueType == pcommon.ValueTypeMap || valueType == pcommon.ValueTypeSlice {\n\t\tstrColumnType = \"complex\"\n\t}\n\tcolumnPrefix := \"\"\n\tif prefix != \"\" {\n\t\tcolumnPrefix = prefix + \"_\"\n\t}\n\tappendNewlineAndIndent(q, indent)\n\tq.WriteString(\"arrayExists((key, value) -> key = ? AND value = ?, s.\" + columnPrefix + strColumnType + \"_attributes.key, s.\" + columnPrefix + strColumnType + \"_attributes.value)\")\n}\n\n// appendNestedArrayExists appends a condition that checks for a key-value pair in nested array attributes.\n// Events and links are stored as nested arrays within spans, so we need to use a nested arrayExists to search\n// through all items and their attributes.\nfunc appendNestedArrayExists(q *strings.Builder, indent int, nestedArray string, valueType pcommon.ValueType) {\n\tstrColumnType := jptrace.ValueTypeToString(valueType)\n\tif valueType == pcommon.ValueTypeBytes || valueType == pcommon.ValueTypeMap || valueType == pcommon.ValueTypeSlice {\n\t\tstrColumnType = \"complex\"\n\t}\n\tappendNewlineAndIndent(q, indent)\n\tq.WriteString(\"arrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.\" + strColumnType + \"_attributes.key, x.\" + strColumnType + \"_attributes.value), s.\" + nestedArray + \")\")\n}\n\nfunc appendStringAttributeFallback(q *strings.Builder, args []any, key string, attr pcommon.Value) []any {\n\tappendArrayExists(q, 2, \"\", pcommon.ValueTypeStr)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendArrayExists(q, 2, \"resource\", pcommon.ValueTypeStr)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendArrayExists(q, 2, \"scope\", pcommon.ValueTypeStr)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendNestedArrayExists(q, 2, \"events\", pcommon.ValueTypeStr)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendNestedArrayExists(q, 2, \"links\", pcommon.ValueTypeStr)\n\treturn append(args, key, attr.Str(), key, attr.Str(), key, attr.Str(), key, attr.Str(), key, attr.Str())\n}\n\nfunc buildGetTracesQuery(params tracestore.GetTraceParams) (string, []any) {\n\tvar q strings.Builder\n\tq.WriteString(sql.SelectSpansByTraceID)\n\targs := []any{params.TraceID}\n\n\tif !params.Start.IsZero() {\n\t\tq.WriteString(\" AND s.start_time >= ?\")\n\t\targs = append(args, params.Start)\n\t}\n\tif !params.End.IsZero() {\n\t\tq.WriteString(\" AND s.start_time <= ?\")\n\t\targs = append(args, params.End)\n\t}\n\n\treturn q.String(), args\n}\n\nfunc buildFindTracesQuery(traceIDsQuery string) string {\n\tinner := indentBlock(\"SELECT trace_id FROM (\\n\" + indentBlock(strings.TrimSpace(traceIDsQuery)) + \"\\n)\")\n\tbase := strings.TrimRight(sql.SelectSpansQuery, \"\\n\")\n\treturn base + \"\\nWHERE s.trace_id IN (\\n\" + inner + \"\\n)\\nORDER BY s.trace_id\"\n}\n\nfunc (r *Reader) buildFindTraceIDsQuery(\n\tctx context.Context,\n\tquery tracestore.TraceQueryParams,\n) (string, []any, error) {\n\tlimit := query.SearchDepth\n\tif limit == 0 {\n\t\tlimit = r.config.DefaultSearchDepth\n\t}\n\tif limit > r.config.MaxSearchDepth {\n\t\treturn \"\", nil, fmt.Errorf(\"search depth %d exceeds maximum allowed %d\", limit, r.config.MaxSearchDepth)\n\t}\n\n\t// Build the inner subquery that finds distinct trace IDs from spans.\n\tvar inner strings.Builder\n\tinner.WriteString(sql.SearchTraceIDsBase)\n\targs := []any{}\n\n\tif query.ServiceName != \"\" {\n\t\tappendAnd(&inner, \"s.service_name = ?\")\n\t\targs = append(args, query.ServiceName)\n\t}\n\tif query.OperationName != \"\" {\n\t\tappendAnd(&inner, \"s.name = ?\")\n\t\targs = append(args, query.OperationName)\n\t}\n\tif query.DurationMin > 0 {\n\t\tappendAnd(&inner, \"s.duration >= ?\")\n\t\targs = append(args, query.DurationMin.Nanoseconds())\n\t}\n\tif query.DurationMax > 0 {\n\t\tappendAnd(&inner, \"s.duration <= ?\")\n\t\targs = append(args, query.DurationMax.Nanoseconds())\n\t}\n\tif !query.StartTimeMin.IsZero() {\n\t\tappendAnd(&inner, \"s.start_time >= ?\")\n\t\targs = append(args, query.StartTimeMin)\n\t}\n\tif !query.StartTimeMax.IsZero() {\n\t\tappendAnd(&inner, \"s.start_time <= ?\")\n\t\targs = append(args, query.StartTimeMax)\n\t}\n\n\tattributeMetadata, err := r.getAttributeMetadata(ctx, query.Attributes)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"failed to get attribute metadata: %w\", err)\n\t}\n\n\targs, err = buildAttributeConditions(&inner, args, query.Attributes, attributeMetadata)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tinner.WriteString(\"\\nLIMIT ?\")\n\targs = append(args, limit)\n\n\t// Wrap the inner subquery with a JOIN to trace_id_timestamps\n\t// to retrieve start/end times only for the limited set of trace IDs.\n\tq := fmt.Sprintf(sql.SearchTraceIDs, indentBlock(inner.String()))\n\n\treturn q, args, nil\n}\n\nfunc buildAttributeConditions(q *strings.Builder, args []any, attributes pcommon.Map, metadata attributeMetadata) ([]any, error) {\n\tfor key, attr := range attributes.All() {\n\t\tappendAnd(q, \"(\")\n\n\t\tvar err error\n\t\tswitch attr.Type() {\n\t\tcase pcommon.ValueTypeBool:\n\t\t\targs = buildSimpleAttributeCondition(q, args, key, pcommon.ValueTypeBool, attr.Bool())\n\t\tcase pcommon.ValueTypeDouble:\n\t\t\targs = buildSimpleAttributeCondition(q, args, key, pcommon.ValueTypeDouble, attr.Double())\n\t\tcase pcommon.ValueTypeInt:\n\t\t\targs = buildSimpleAttributeCondition(q, args, key, pcommon.ValueTypeInt, attr.Int())\n\t\tcase pcommon.ValueTypeStr:\n\t\t\targs = buildStringAttributeCondition(q, args, key, attr, metadata)\n\t\tcase pcommon.ValueTypeBytes:\n\t\t\targs = buildBytesAttributeCondition(q, args, key, attr)\n\t\tcase pcommon.ValueTypeSlice:\n\t\t\targs, err = buildSliceAttributeCondition(q, args, key, attr)\n\t\t\tif err != nil {\n\t\t\t\treturn args, err\n\t\t\t}\n\t\tcase pcommon.ValueTypeMap:\n\t\t\targs, err = buildMapAttributeCondition(q, args, key, attr)\n\t\t\tif err != nil {\n\t\t\t\treturn args, err\n\t\t\t}\n\t\tdefault:\n\t\t\treturn args, fmt.Errorf(\"unsupported attribute type %v for key %s\", attr.Type(), key)\n\t\t}\n\n\t\tappendNewlineAndIndent(q, 1)\n\t\tq.WriteString(\")\")\n\t}\n\n\treturn args, nil\n}\n\nfunc buildSimpleAttributeCondition(q *strings.Builder, args []any, key string, valueType pcommon.ValueType, value any) []any {\n\tappendArrayExists(q, 2, \"\", valueType)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendArrayExists(q, 2, \"resource\", valueType)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendNestedArrayExists(q, 2, \"events\", valueType)\n\tappendNewlineAndIndent(q, 2)\n\tq.WriteString(\"OR \")\n\tappendNestedArrayExists(q, 2, \"links\", valueType)\n\treturn append(args, key, value, key, value, key, value, key, value)\n}\n\nfunc buildBytesAttributeCondition(q *strings.Builder, args []any, key string, attr pcommon.Value) []any {\n\treturn buildSimpleAttributeCondition(q, args, \"@bytes@\"+key, pcommon.ValueTypeBytes, base64.StdEncoding.EncodeToString(attr.Bytes().AsRaw()))\n}\n\nfunc buildSliceAttributeCondition(q *strings.Builder, args []any, key string, attr pcommon.Value) ([]any, error) {\n\tb, err := marshalValueForQuery(attr)\n\tif err != nil {\n\t\treturn args, fmt.Errorf(\"failed to marshal slice attribute %q: %w\", key, err)\n\t}\n\treturn buildSimpleAttributeCondition(q, args, \"@slice@\"+key, pcommon.ValueTypeSlice, b), nil\n}\n\nfunc buildMapAttributeCondition(q *strings.Builder, args []any, key string, attr pcommon.Value) ([]any, error) {\n\tb, err := marshalValueForQuery(attr)\n\tif err != nil {\n\t\treturn args, fmt.Errorf(\"failed to marshal map attribute %q: %w\", key, err)\n\t}\n\treturn buildSimpleAttributeCondition(q, args, \"@map@\"+key, pcommon.ValueTypeMap, b), nil\n}\n\nfunc parseStringToTypedValue(key string, attr pcommon.Value, t pcommon.ValueType) (typedAttributeValue, error) {\n\tswitch t {\n\tcase pcommon.ValueTypeBool:\n\t\tb, parseErr := strconv.ParseBool(attr.Str())\n\t\tif parseErr != nil {\n\t\t\treturn typedAttributeValue{}, fmt.Errorf(\"failed to parse bool attribute %q: %w\", key, parseErr)\n\t\t}\n\t\treturn typedAttributeValue{key: key, value: b, valueType: t}, nil\n\tcase pcommon.ValueTypeDouble:\n\t\tf, parseErr := strconv.ParseFloat(attr.Str(), 64)\n\t\tif parseErr != nil {\n\t\t\treturn typedAttributeValue{}, fmt.Errorf(\"failed to parse double attribute %q: %w\", key, parseErr)\n\t\t}\n\t\treturn typedAttributeValue{key: key, value: f, valueType: t}, nil\n\tcase pcommon.ValueTypeInt:\n\t\ti, parseErr := strconv.ParseInt(attr.Str(), 10, 64)\n\t\tif parseErr != nil {\n\t\t\treturn typedAttributeValue{}, fmt.Errorf(\"failed to parse int attribute %q: %w\", key, parseErr)\n\t\t}\n\t\treturn typedAttributeValue{key: key, value: i, valueType: t}, nil\n\tcase pcommon.ValueTypeStr:\n\t\treturn typedAttributeValue{key: key, value: attr.Str(), valueType: t}, nil\n\tcase pcommon.ValueTypeBytes:\n\t\treturn typedAttributeValue{key: \"@bytes@\" + key, value: attr.Str(), valueType: t}, nil\n\tcase pcommon.ValueTypeMap:\n\t\treturn typedAttributeValue{key: \"@map@\" + key, value: attr.Str(), valueType: t}, nil\n\tcase pcommon.ValueTypeSlice:\n\t\treturn typedAttributeValue{key: \"@slice@\" + key, value: attr.Str(), valueType: t}, nil\n\tdefault:\n\t\treturn typedAttributeValue{}, fmt.Errorf(\"unsupported attribute type %v for key %q\", t, key)\n\t}\n}\n\n// buildStringAttributeCondition adds a condition for string attributes by looking up their\n// actual stored type(s) and level(s) from the attribute_metadata table.\n//\n// String attributes require special handling because the query service passes all\n// attributes as strings (via AsString()), regardless of their actual stored type.\n// We must look up the attribute_metadata to determine the actual type(s) and\n// level(s) where this attribute is stored, then convert the string back to the\n// appropriate type for querying.\n//\n// If metadata exists but the value cannot be parsed as any of the metadata types,\n// we fall back to treating it as a string attribute.\nfunc buildStringAttributeCondition(\n\tq *strings.Builder,\n\targs []any,\n\tkey string,\n\tattr pcommon.Value,\n\tmetadata attributeMetadata,\n) []any {\n\tlevelTypes, ok := metadata[key]\n\n\t// if no metadata found, assume string type\n\tif !ok {\n\t\treturn appendStringAttributeFallback(q, args, key, attr)\n\t}\n\n\tgeneratedCondition := false\n\tappendLevel := func(types []pcommon.ValueType, prefix string, fn arrayExistsFn) {\n\t\tfor _, t := range types {\n\t\t\ttav, err := parseStringToTypedValue(key, attr, t)\n\t\t\tif err != nil {\n\t\t\t\t// Skip types that can't parse this value\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif generatedCondition {\n\t\t\t\tappendNewlineAndIndent(q, 2)\n\t\t\t\tq.WriteString(\"OR \")\n\t\t\t}\n\t\t\tgeneratedCondition = true\n\n\t\t\tfn(q, 2, prefix, tav.valueType)\n\t\t\targs = append(args, tav.key, tav.value)\n\t\t}\n\t}\n\n\tappendLevel(levelTypes.resource, \"resource\", appendArrayExists)\n\tappendLevel(levelTypes.scope, \"scope\", appendArrayExists)\n\tappendLevel(levelTypes.span, \"\", appendArrayExists)\n\tappendLevel(levelTypes.event, \"events\", appendNestedArrayExists)\n\tappendLevel(levelTypes.link, \"links\", appendNestedArrayExists)\n\n\t// If no conditions were generated (all types failed to parse),\n\t// fall back to treating it as a string attribute\n\tif !generatedCondition {\n\t\treturn appendStringAttributeFallback(q, args, key, attr)\n\t}\n\treturn args\n}\n\nfunc buildSelectAttributeMetadataQuery(attributes pcommon.Map) (string, []any) {\n\targs := []any{}\n\tvar placeholders []string\n\n\tfor key, attr := range attributes.All() {\n\t\tif attr.Type() == pcommon.ValueTypeStr {\n\t\t\tplaceholders = append(placeholders, \"?\")\n\t\t\targs = append(args, key)\n\t\t}\n\t}\n\n\tvar q strings.Builder\n\tq.WriteString(sql.SelectAttributeMetadata)\n\tif len(placeholders) > 0 {\n\t\tappendNewlineAndIndent(&q, 0)\n\t\tq.WriteString(\"WHERE\")\n\t\tappendNewlineAndIndent(&q, 1)\n\t\tq.WriteString(\"attribute_key IN (\")\n\t\tq.WriteString(strings.Join(placeholders, \", \"))\n\t\tq.WriteString(\")\")\n\t}\n\tappendNewlineAndIndent(&q, 0)\n\tq.WriteString(\"GROUP BY\")\n\tappendNewlineAndIndent(&q, 1)\n\tq.WriteString(\"attribute_key,\")\n\tappendNewlineAndIndent(&q, 1)\n\tq.WriteString(\"type,\")\n\tappendNewlineAndIndent(&q, 1)\n\tq.WriteString(\"level\")\n\treturn q.String(), args\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/query_builder_test.go",
    "content": "// Copyright (c) 2026 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n)\n\nfunc TestBuildFindTraceIDsQuery_MarshalErrors(t *testing.T) {\n\torig := marshalValueForQuery\n\tt.Cleanup(func() { marshalValueForQuery = orig })\n\tmarshalValueForQuery = func(pcommon.Value) (string, error) {\n\t\treturn \"\", assert.AnError\n\t}\n\n\tt.Run(\"marshal slice error\", func(t *testing.T) {\n\t\tattrs := pcommon.NewMap()\n\t\ts := attrs.PutEmptySlice(\"bad_slice\")\n\t\ts.AppendEmpty()\n\n\t\treader := NewReader(&testDriver{t: t}, testReaderConfig)\n\t\t_, _, err := reader.buildFindTraceIDsQuery(t.Context(), tracestore.TraceQueryParams{Attributes: attrs})\n\n\t\trequire.Error(t, err)\n\t\trequire.ErrorContains(t, err, \"failed to marshal slice attribute\")\n\t})\n\n\tt.Run(\"marshal map error\", func(t *testing.T) {\n\t\tattrs := pcommon.NewMap()\n\t\tm := attrs.PutEmptyMap(\"bad_map\")\n\t\tm.PutEmpty(\"key\")\n\n\t\treader := NewReader(&testDriver{t: t}, testReaderConfig)\n\t\t_, _, err := reader.buildFindTraceIDsQuery(t.Context(), tracestore.TraceQueryParams{Attributes: attrs})\n\n\t\trequire.Error(t, err)\n\t\trequire.ErrorContains(t, err, \"failed to marshal map attribute\")\n\t})\n}\n\nfunc TestBuildFindTraceIDsQuery_AttributeMetadataError(t *testing.T) {\n\ttd := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\trows: nil,\n\t\t\t\terr:  assert.AnError,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(td, testReaderConfig)\n\t_, _, err := reader.buildFindTraceIDsQuery(t.Context(), tracestore.TraceQueryParams{Attributes: buildTestAttributes()})\n\trequire.ErrorContains(t, err, \"failed to get attribute metadata\")\n}\n\nfunc TestBuildStringAttributeCondition_Fallbacks(t *testing.T) {\n\tcases := []struct {\n\t\tname      string\n\t\tattrValue string\n\t\tmetadata  attributeMetadata\n\t}{\n\t\t{\n\t\t\tname:      \"parse bool fails falls back to str\",\n\t\t\tattrValue: \"not-bool\",\n\t\t\tmetadata: attributeMetadata{\n\t\t\t\t\"k\": {span: []pcommon.ValueType{pcommon.ValueTypeBool}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"parse double fails falls back to str\",\n\t\t\tattrValue: \"not-float\",\n\t\t\tmetadata: attributeMetadata{\n\t\t\t\t\"k\": {span: []pcommon.ValueType{pcommon.ValueTypeDouble}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"parse int fails falls back to str\",\n\t\t\tattrValue: \"not-int\",\n\t\t\tmetadata: attributeMetadata{\n\t\t\t\t\"k\": {span: []pcommon.ValueType{pcommon.ValueTypeInt}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"unsupported type falls back to str\",\n\t\t\tattrValue: \"whatever\",\n\t\t\tmetadata: attributeMetadata{\n\t\t\t\t\"k\": {span: []pcommon.ValueType{pcommon.ValueTypeEmpty}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tattr := pcommon.NewValueStr(tc.attrValue)\n\t\t\tvar q strings.Builder\n\t\t\tvar args []any\n\n\t\t\targs = buildStringAttributeCondition(&q, args, \"k\", attr, tc.metadata)\n\n\t\t\tquery := q.String()\n\t\t\tassert.Contains(t, query, \"str_attributes\")\n\t\t\tassert.Contains(t, query, \"resource_str_attributes\")\n\t\t\tassert.Contains(t, query, \"scope_str_attributes\")\n\t\t\tassert.Contains(t, query, \"events\")\n\t\t\tassert.Contains(t, query, \"links\")\n\t\t\tassert.Len(t, args, 10)\n\t\t})\n\t}\n}\n\nfunc TestBuildGetTracesQuery(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tparams       tracestore.GetTraceParams\n\t\texpectedSQL  string\n\t\texpectedArgs []any\n\t}{\n\t\t{\n\t\t\tname: \"without time range\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t},\n\t\t\texpectedSQL:  sql.SelectSpansByTraceID,\n\t\t\texpectedArgs: []any{traceID},\n\t\t},\n\t\t{\n\t\t\tname: \"with both start and end\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t\tStart:   now.Add(-1 * time.Hour),\n\t\t\t\tEnd:     now,\n\t\t\t},\n\t\t\texpectedSQL:  sql.SelectSpansByTraceID + \" AND s.start_time >= ? AND s.start_time <= ?\",\n\t\t\texpectedArgs: []any{traceID, now.Add(-1 * time.Hour), now},\n\t\t},\n\t\t{\n\t\t\tname: \"with only start time\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t\tStart:   now.Add(-1 * time.Hour),\n\t\t\t},\n\t\t\texpectedSQL:  sql.SelectSpansByTraceID + \" AND s.start_time >= ?\",\n\t\t\texpectedArgs: []any{traceID, now.Add(-1 * time.Hour)},\n\t\t},\n\t\t{\n\t\t\tname: \"with only end time\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t\tEnd:     now,\n\t\t\t},\n\t\t\texpectedSQL:  sql.SelectSpansByTraceID + \" AND s.start_time <= ?\",\n\t\t\texpectedArgs: []any{traceID, now},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tquery, args := buildGetTracesQuery(tt.params)\n\t\t\trequire.Equal(t, tt.expectedSQL, query)\n\t\t\trequire.Equal(t, tt.expectedArgs, args)\n\t\t})\n\t}\n}\n\nfunc TestBuildStringAttributeCondition_MultipleTypes(t *testing.T) {\n\tattr := pcommon.NewValueStr(\"123\") // parses as both int and str\n\tvar q strings.Builder\n\tvar args []any\n\n\tmetadata := attributeMetadata{\n\t\t\"http.status\": {span: []pcommon.ValueType{pcommon.ValueTypeInt, pcommon.ValueTypeStr}},\n\t}\n\n\targs = buildStringAttributeCondition(&q, args, \"http.status\", attr, metadata)\n\n\tquery := q.String()\n\tassert.Contains(t, query, \"int_attributes\")\n\tassert.Contains(t, query, \"OR\")\n\tassert.Contains(t, query, \"str_attributes\")\n\tassert.Len(t, args, 4)\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/reader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"iter\"\n\t\"time\"\n\n\t\"github.com/ClickHouse/clickhouse-go/v2/lib/driver\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\nvar _ tracestore.Reader = (*Reader)(nil)\n\ntype ReaderConfig struct {\n\t// DefaultSearchDepth is the default number of trace IDs to return when searching for traces.\n\t// This value is used when the SearchDepth field in TraceQueryParams is not set.\n\tDefaultSearchDepth int\n\t// MaxSearchDepth is the maximum number of trace IDs that can be returned when searching for traces.\n\t// This value is used to limit the SearchDepth field in TraceQueryParams.\n\tMaxSearchDepth int\n}\n\ntype Reader struct {\n\tconn   driver.Conn\n\tconfig ReaderConfig\n}\n\n// NewReader returns a new Reader instance that uses the given ClickHouse connection\n// to read trace data.\n//\n// The provided connection is used exclusively for reading traces, meaning it is safe\n// to enable instrumentation on the connection without risk of recursively generating traces.\nfunc NewReader(conn driver.Conn, cfg ReaderConfig) *Reader {\n\treturn &Reader{conn: conn, config: cfg}\n}\n\nfunc (r *Reader) GetTraces(\n\tctx context.Context,\n\ttraceIDs ...tracestore.GetTraceParams,\n) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tfor _, traceID := range traceIDs {\n\t\t\tquery, args := buildGetTracesQuery(traceID)\n\t\t\trows, err := r.conn.Query(ctx, query, args...)\n\t\t\tif err != nil {\n\t\t\t\tyield(nil, fmt.Errorf(\"failed to query trace: %w\", err))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdone := false\n\t\t\tfor rows.Next() {\n\t\t\t\tspan, err := dbmodel.ScanRow(rows)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !yield(nil, fmt.Errorf(\"failed to scan span row: %w\", err)) {\n\t\t\t\t\t\tdone = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ttrace := dbmodel.FromRow(span)\n\t\t\t\tif !yield([]ptrace.Traces{trace}, nil) {\n\t\t\t\t\tdone = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err := rows.Close(); err != nil {\n\t\t\t\tyield(nil, fmt.Errorf(\"failed to close rows: %w\", err))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif done {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (r *Reader) GetServices(ctx context.Context) ([]string, error) {\n\trows, err := r.conn.Query(ctx, sql.SelectServices)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query services: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar services []string\n\tfor rows.Next() {\n\t\tvar service dbmodel.Service\n\t\tif err := rows.ScanStruct(&service); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to scan row: %w\", err)\n\t\t}\n\t\tservices = append(services, service.Name)\n\t}\n\treturn services, nil\n}\n\nfunc (r *Reader) GetOperations(\n\tctx context.Context,\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\tvar rows driver.Rows\n\tvar err error\n\tif query.SpanKind == \"\" {\n\t\trows, err = r.conn.Query(ctx, sql.SelectOperationsAllKinds, query.ServiceName)\n\t} else {\n\t\trows, err = r.conn.Query(ctx, sql.SelectOperationsByKind, query.ServiceName, query.SpanKind)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query operations: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar operations []tracestore.Operation\n\tfor rows.Next() {\n\t\tvar operation dbmodel.Operation\n\t\tif err := rows.ScanStruct(&operation); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to scan row: %w\", err)\n\t\t}\n\t\to := tracestore.Operation{\n\t\t\tName:     operation.Name,\n\t\t\tSpanKind: operation.SpanKind,\n\t\t}\n\t\toperations = append(operations, o)\n\t}\n\treturn operations, nil\n}\n\nfunc (r *Reader) FindTraces(\n\tctx context.Context,\n\tquery tracestore.TraceQueryParams,\n) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\ttraceIDsQuery, args, err := r.buildFindTraceIDsQuery(ctx, query)\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to build query: %w\", err))\n\t\t\treturn\n\t\t}\n\n\t\trows, err := r.conn.Query(ctx, buildFindTracesQuery(traceIDsQuery), args...)\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to query traces: %w\", err))\n\t\t\treturn\n\t\t}\n\t\tdefer rows.Close()\n\n\t\tfor rows.Next() {\n\t\t\tspan, err := dbmodel.ScanRow(rows)\n\t\t\tif err != nil {\n\t\t\t\tif !yield(nil, fmt.Errorf(\"failed to scan span row: %w\", err)) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttrace := dbmodel.FromRow(span)\n\t\t\tif !yield([]ptrace.Traces{trace}, nil) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc readRowIntoTraceID(rows driver.Rows) ([]tracestore.FoundTraceID, error) {\n\tvar traceIDHex string\n\tvar start, end time.Time\n\n\tif err := rows.Scan(&traceIDHex, &start, &end); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to scan row: %w\", err)\n\t}\n\n\tb, err := hex.DecodeString(traceIDHex)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode trace ID: %w\", err)\n\t}\n\n\ttraceID := tracestore.FoundTraceID{\n\t\tTraceID: pcommon.TraceID(b),\n\t}\n\n\tif !start.IsZero() {\n\t\ttraceID.Start = start\n\t}\n\tif !end.IsZero() {\n\t\ttraceID.End = end\n\t}\n\n\treturn []tracestore.FoundTraceID{\n\t\ttraceID,\n\t}, nil\n}\n\nfunc (r *Reader) FindTraceIDs(\n\tctx context.Context,\n\tquery tracestore.TraceQueryParams,\n) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\treturn func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\tq, args, err := r.buildFindTraceIDsQuery(ctx, query)\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to build query: %w\", err))\n\t\t\treturn\n\t\t}\n\n\t\trows, err := r.conn.Query(ctx, q, args...)\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to query trace IDs: %w\", err))\n\t\t\treturn\n\t\t}\n\t\tdefer rows.Close()\n\n\t\tfor rows.Next() {\n\t\t\ttraceID, err := readRowIntoTraceID(rows)\n\t\t\tif !yield(traceID, err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/reader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jiter\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\nvar (\n\ttestReaderConfig = ReaderConfig{\n\t\tDefaultSearchDepth: 100,\n\t\tMaxSearchDepth:     1000,\n\t}\n\ttestTraceIDsData = [][]any{\n\t\t{\n\t\t\ttraceIDHex1,\n\t\t\tnow.Add(-1 * time.Hour),\n\t\t\tnow,\n\t\t},\n\t\t{\n\t\t\ttraceIDHex2,\n\t\t\ttime.Time{},\n\t\t\ttime.Time{},\n\t\t},\n\t}\n\ttestAttributeMetadata = []dbmodel.AttributeMetadata{\n\t\t{AttributeKey: \"span.flag\", Type: \"bool\", Level: \"span\"},\n\t\t{AttributeKey: \"resource.latency\", Type: \"double\", Level: \"resource\"},\n\t\t{AttributeKey: \"scope.attempt\", Type: \"int\", Level: \"scope\"},\n\t\t{AttributeKey: \"http.method\", Type: \"str\", Level: \"span\"},\n\t\t{AttributeKey: \"http.method\", Type: \"int\", Level: \"span\"},\n\t\t{AttributeKey: \"resource.checksum\", Type: \"bytes\", Level: \"resource\"},\n\t\t{AttributeKey: \"metadata\", Type: \"map\", Level: \"span\"},\n\t\t{AttributeKey: \"tags\", Type: \"slice\", Level: \"span\"},\n\t\t{AttributeKey: \"event.attr\", Type: \"str\", Level: \"event\"},\n\t}\n)\n\nfunc buildTestAttributes() pcommon.Map {\n\tattrs := pcommon.NewMap()\n\tattrs.PutBool(\"login_successful\", true)\n\tattrs.PutDouble(\"response_time\", 0.123)\n\tattrs.PutInt(\"attempt_count\", 1)\n\tb := attrs.PutEmptyBytes(\"file.checksum\")\n\ts := attrs.PutEmptySlice(\"http.headers\")\n\tm := attrs.PutEmptyMap(\"http.cookies\")\n\n\tb.FromRaw([]byte{0x12, 0x34, 0x56, 0x78})\n\ts.AppendEmpty().SetStr(\"header1: value1\")\n\tm.PutStr(\"session_id\", \"abc123\")\n\n\t// these attributes will require type lookup from attribute_metadata\n\tattrs.PutStr(\"no.metadata\", \"nonexistent\") // no metadata entry\n\tattrs.PutStr(\"http.method\", \"GET\")\n\tattrs.PutStr(\"span.flag\", \"true\")\n\tattrs.PutStr(\"resource.latency\", \"0.5\")\n\tattrs.PutStr(\"scope.attempt\", \"7\")\n\tattrs.PutStr(\"resource.checksum\", \"EjRWeA==\")\n\tattrs.PutStr(\"metadata\", \"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\")\n\tattrs.PutStr(\"tags\", \"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\")\n\tattrs.PutStr(\"event.attr\", \"event-value\")\n\n\treturn attrs\n}\n\nfunc scanSpanRowFn() func(dest any, src *dbmodel.SpanRow) error {\n\treturn func(dest any, src *dbmodel.SpanRow) error {\n\t\tptrs, ok := dest.([]any)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected []any for dest, got %T\", dest)\n\t\t}\n\t\tif len(ptrs) != 68 {\n\t\t\treturn fmt.Errorf(\"expected 68 destination arguments, got %d\", len(ptrs))\n\t\t}\n\n\t\tvalues := []any{\n\t\t\t&src.ID,\n\t\t\t&src.TraceID,\n\t\t\t&src.TraceState,\n\t\t\t&src.ParentSpanID,\n\t\t\t&src.Name,\n\t\t\t&src.Kind,\n\t\t\t&src.StartTime,\n\t\t\t&src.StatusCode,\n\t\t\t&src.StatusMessage,\n\t\t\t&src.Duration,\n\t\t\t&src.Attributes.BoolKeys,\n\t\t\t&src.Attributes.BoolValues,\n\t\t\t&src.Attributes.DoubleKeys,\n\t\t\t&src.Attributes.DoubleValues,\n\t\t\t&src.Attributes.IntKeys,\n\t\t\t&src.Attributes.IntValues,\n\t\t\t&src.Attributes.StrKeys,\n\t\t\t&src.Attributes.StrValues,\n\t\t\t&src.Attributes.ComplexKeys,\n\t\t\t&src.Attributes.ComplexValues,\n\t\t\t&src.EventNames,\n\t\t\t&src.EventTimestamps,\n\t\t\t&src.EventAttributes.BoolKeys,\n\t\t\t&src.EventAttributes.BoolValues,\n\t\t\t&src.EventAttributes.DoubleKeys,\n\t\t\t&src.EventAttributes.DoubleValues,\n\t\t\t&src.EventAttributes.IntKeys,\n\t\t\t&src.EventAttributes.IntValues,\n\t\t\t&src.EventAttributes.StrKeys,\n\t\t\t&src.EventAttributes.StrValues,\n\t\t\t&src.EventAttributes.ComplexKeys,\n\t\t\t&src.EventAttributes.ComplexValues,\n\t\t\t&src.LinkTraceIDs,\n\t\t\t&src.LinkSpanIDs,\n\t\t\t&src.LinkTraceStates,\n\t\t\t&src.LinkAttributes.BoolKeys,\n\t\t\t&src.LinkAttributes.BoolValues,\n\t\t\t&src.LinkAttributes.DoubleKeys,\n\t\t\t&src.LinkAttributes.DoubleValues,\n\t\t\t&src.LinkAttributes.IntKeys,\n\t\t\t&src.LinkAttributes.IntValues,\n\t\t\t&src.LinkAttributes.StrKeys,\n\t\t\t&src.LinkAttributes.StrValues,\n\t\t\t&src.LinkAttributes.ComplexKeys,\n\t\t\t&src.LinkAttributes.ComplexValues,\n\t\t\t&src.ServiceName,\n\t\t\t&src.ResourceAttributes.BoolKeys,\n\t\t\t&src.ResourceAttributes.BoolValues,\n\t\t\t&src.ResourceAttributes.DoubleKeys,\n\t\t\t&src.ResourceAttributes.DoubleValues,\n\t\t\t&src.ResourceAttributes.IntKeys,\n\t\t\t&src.ResourceAttributes.IntValues,\n\t\t\t&src.ResourceAttributes.StrKeys,\n\t\t\t&src.ResourceAttributes.StrValues,\n\t\t\t&src.ResourceAttributes.ComplexKeys,\n\t\t\t&src.ResourceAttributes.ComplexValues,\n\t\t\t&src.ScopeName,\n\t\t\t&src.ScopeVersion,\n\t\t\t&src.ScopeAttributes.BoolKeys,\n\t\t\t&src.ScopeAttributes.BoolValues,\n\t\t\t&src.ScopeAttributes.DoubleKeys,\n\t\t\t&src.ScopeAttributes.DoubleValues,\n\t\t\t&src.ScopeAttributes.IntKeys,\n\t\t\t&src.ScopeAttributes.IntValues,\n\t\t\t&src.ScopeAttributes.StrKeys,\n\t\t\t&src.ScopeAttributes.StrValues,\n\t\t\t&src.ScopeAttributes.ComplexKeys,\n\t\t\t&src.ScopeAttributes.ComplexValues,\n\t\t}\n\n\t\tfor i := range ptrs {\n\t\t\treflect.ValueOf(ptrs[i]).Elem().Set(reflect.ValueOf(values[i]).Elem())\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc scanAttributeMetadataFn() func(dest any, src dbmodel.AttributeMetadata) error {\n\treturn func(dest any, src dbmodel.AttributeMetadata) error {\n\t\tptr, ok := dest.(*dbmodel.AttributeMetadata)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected *dbmodel.AttributeMetadata for dest, got %T\", dest)\n\t\t}\n\t\t*ptr = src\n\t\treturn nil\n\t}\n}\n\nfunc scanTraceIDFn() func(dest any, src []any) error {\n\treturn func(dest any, src []any) error {\n\t\tptrs, ok := dest.([]any)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected []any for dest, got %T\", dest)\n\t\t}\n\t\tif len(ptrs) != 3 {\n\t\t\tfmt.Println(src)\n\t\t\treturn fmt.Errorf(\"expected 3 destination arguments, got %d\", len(ptrs))\n\t\t}\n\n\t\tptr, ok := ptrs[0].(*string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected *string for dest[0], got %T\", ptrs[0])\n\t\t}\n\n\t\tstartPtr, ok := ptrs[1].(*time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected *time.Time for dest[1], got %T\", ptrs[1])\n\t\t}\n\n\t\tendPtr, ok := ptrs[2].(*time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"expected *time.Time for dest[2], got %T\", ptrs[2])\n\t\t}\n\n\t\t*ptr = src[0].(string)\n\t\t*startPtr = src[1].(time.Time)\n\t\t*endPtr = src[2].(time.Time)\n\t\treturn nil\n\t}\n}\n\nfunc TestGetTraces_Success(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tparams tracestore.GetTraceParams\n\t\tdata   []*dbmodel.SpanRow\n\t}{\n\t\t{\n\t\t\tname: \"single span\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t},\n\t\t\tdata: singleSpan,\n\t\t},\n\t\t{\n\t\t\tname: \"multiple spans\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t},\n\t\t\tdata: multipleSpans,\n\t\t},\n\t\t{\n\t\t\tname: \"with time range\",\n\t\t\tparams: tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t\tStart:   now.Add(-1 * time.Hour),\n\t\t\t\tEnd:     now,\n\t\t\t},\n\t\t\tdata: singleSpan,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconn := &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansByTraceID: {\n\t\t\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\t\t\tdata:   tt.data,\n\t\t\t\t\t\t\tscanFn: scanSpanRowFn(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\treader := NewReader(conn, testReaderConfig)\n\t\t\tgetTracesIter := reader.GetTraces(context.Background(), tt.params)\n\t\t\ttraces, err := jiter.FlattenWithErrors(getTracesIter)\n\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, conn.recordedQueries, 1)\n\t\t\tverifyQuerySnapshot(t, conn.recordedQueries...)\n\t\t\trequireTracesEqual(t, tt.data, traces)\n\t\t})\n\t}\n}\n\nfunc TestGetTraces_ErrorCases(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdriver      *testDriver\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"QueryError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansByTraceID: {\n\t\t\t\t\t\trows: nil,\n\t\t\t\t\t\terr:  assert.AnError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to query trace\",\n\t\t},\n\t\t{\n\t\t\tname: \"ScanError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansByTraceID: {\n\t\t\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\t\t\tdata:    singleSpan,\n\t\t\t\t\t\t\tscanErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to scan span row\",\n\t\t},\n\t\t{\n\t\t\tname: \"CloseError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansByTraceID: {\n\t\t\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\t\t\tdata:     singleSpan,\n\t\t\t\t\t\t\tscanFn:   scanSpanRowFn(),\n\t\t\t\t\t\t\tcloseErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to close rows\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := NewReader(test.driver, testReaderConfig)\n\t\t\titer := reader.GetTraces(context.Background(), tracestore.GetTraceParams{\n\t\t\t\tTraceID: traceID,\n\t\t\t})\n\t\t\t_, err := jiter.FlattenWithErrors(iter)\n\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t})\n\t}\n}\n\nfunc TestGetTraces_ScanErrorContinues(t *testing.T) {\n\tscanCalled := 0\n\n\tscanFn := func(dest any, src *dbmodel.SpanRow) error {\n\t\tscanCalled++\n\t\tif scanCalled == 1 {\n\t\t\treturn assert.AnError // simulate scan error on the first row\n\t\t}\n\t\treturn scanSpanRowFn()(dest, src)\n\t}\n\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectSpansByTraceID: {\n\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\tdata:   multipleSpans,\n\t\t\t\t\tscanFn: scanFn,\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tgetTracesIter := reader.GetTraces(context.Background(), tracestore.GetTraceParams{\n\t\tTraceID: traceID,\n\t})\n\n\texpected := multipleSpans[1:] // skip the first span which caused the error\n\tfor trace, err := range getTracesIter {\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, assert.AnError)\n\t\t\tcontinue\n\t\t}\n\t\trequireTracesEqual(t, expected, trace)\n\t}\n}\n\nfunc TestGetTraces_YieldFalseOnSuccessStopsIteration(t *testing.T) {\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectSpansByTraceID: {\n\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\tdata:   multipleSpans,\n\t\t\t\t\tscanFn: scanSpanRowFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tgetTracesIter := reader.GetTraces(context.Background(), tracestore.GetTraceParams{\n\t\tTraceID: traceID,\n\t})\n\n\tvar gotTraces []ptrace.Traces\n\tgetTracesIter(func(traces []ptrace.Traces, err error) bool {\n\t\trequire.NoError(t, err)\n\t\tgotTraces = append(gotTraces, traces...)\n\t\treturn false // stop iteration after the first span\n\t})\n\n\trequire.Len(t, gotTraces, 1)\n\trequireTracesEqual(t, multipleSpans[0:1], gotTraces)\n}\n\nfunc TestGetServices(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tconn        *testDriver\n\t\texpected    []string\n\t\texpectError string\n\t}{\n\t\t{\n\t\t\tname: \"successfully returns services\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectServices: {\n\t\t\t\t\t\trows: &testRows[dbmodel.Service]{\n\t\t\t\t\t\t\tdata: []dbmodel.Service{\n\t\t\t\t\t\t\t\t{Name: \"serviceA\"},\n\t\t\t\t\t\t\t\t{Name: \"serviceB\"},\n\t\t\t\t\t\t\t\t{Name: \"serviceC\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tscanFn: func(dest any, src dbmodel.Service) error {\n\t\t\t\t\t\t\t\tsvc, ok := dest.(*dbmodel.Service)\n\t\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\t\treturn errors.New(\"dest is not *dbmodel.Service\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t*svc = src\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: []string{\"serviceA\", \"serviceB\", \"serviceC\"},\n\t\t},\n\t\t{\n\t\t\tname: \"query error\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectServices: {\n\t\t\t\t\t\trows: nil,\n\t\t\t\t\t\terr:  assert.AnError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: \"failed to query services\",\n\t\t},\n\t\t{\n\t\t\tname: \"scan error\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectServices: {\n\t\t\t\t\t\trows: &testRows[dbmodel.Service]{\n\t\t\t\t\t\t\tdata: []dbmodel.Service{\n\t\t\t\t\t\t\t\t{Name: \"serviceA\"},\n\t\t\t\t\t\t\t\t{Name: \"serviceB\"},\n\t\t\t\t\t\t\t\t{Name: \"serviceC\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tscanErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: \"failed to scan row\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := NewReader(test.conn, testReaderConfig)\n\n\t\t\tresult, err := reader.GetServices(context.Background())\n\n\t\t\tif test.expectError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Len(t, test.conn.recordedQueries, 1)\n\t\t\t\tverifyQuerySnapshot(t, test.conn.recordedQueries...)\n\t\t\t\trequire.Equal(t, test.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetOperations(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tconn        *testDriver\n\t\tquery       tracestore.OperationQueryParams\n\t\texpected    []tracestore.Operation\n\t\texpectError string\n\t}{\n\t\t{\n\t\t\tname: \"successfully returns operations for all kinds\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectOperationsAllKinds: {\n\t\t\t\t\t\trows: &testRows[dbmodel.Operation]{\n\t\t\t\t\t\t\tdata: []dbmodel.Operation{\n\t\t\t\t\t\t\t\t{Name: \"operationA\"},\n\t\t\t\t\t\t\t\t{Name: \"operationB\"},\n\t\t\t\t\t\t\t\t{Name: \"operationC\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tscanFn: func(dest any, src dbmodel.Operation) error {\n\t\t\t\t\t\t\t\tsvc, ok := dest.(*dbmodel.Operation)\n\t\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\t\treturn errors.New(\"dest is not *dbmodel.Operation\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t*svc = src\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tquery: tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"serviceA\",\n\t\t\t},\n\t\t\texpected: []tracestore.Operation{\n\t\t\t\t{\n\t\t\t\t\tName: \"operationA\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"operationB\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"operationC\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"successfully returns operations by kind\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectOperationsByKind: {\n\t\t\t\t\t\trows: &testRows[dbmodel.Operation]{\n\t\t\t\t\t\t\tdata: []dbmodel.Operation{\n\t\t\t\t\t\t\t\t{Name: \"operationA\", SpanKind: \"server\"},\n\t\t\t\t\t\t\t\t{Name: \"operationB\", SpanKind: \"server\"},\n\t\t\t\t\t\t\t\t{Name: \"operationC\", SpanKind: \"server\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tscanFn: func(dest any, src dbmodel.Operation) error {\n\t\t\t\t\t\t\t\tsvc, ok := dest.(*dbmodel.Operation)\n\t\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\t\treturn errors.New(\"dest is not *dbmodel.Operation\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t*svc = src\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tquery: tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"serviceA\",\n\t\t\t\tSpanKind:    \"server\",\n\t\t\t},\n\t\t\texpected: []tracestore.Operation{\n\t\t\t\t{\n\t\t\t\t\tName:     \"operationA\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"operationB\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"operationC\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"query error\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectOperationsAllKinds: {\n\t\t\t\t\t\trows: nil,\n\t\t\t\t\t\terr:  assert.AnError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: \"failed to query operations\",\n\t\t},\n\t\t{\n\t\t\tname: \"scan error\",\n\t\t\tconn: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectOperationsAllKinds: {\n\t\t\t\t\t\trows: &testRows[dbmodel.Operation]{\n\t\t\t\t\t\t\tdata: []dbmodel.Operation{\n\t\t\t\t\t\t\t\t{Name: \"operationA\"},\n\t\t\t\t\t\t\t\t{Name: \"operationB\"},\n\t\t\t\t\t\t\t\t{Name: \"operationC\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tscanErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectError: \"failed to scan row\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := NewReader(test.conn, testReaderConfig)\n\n\t\t\tresult, err := reader.GetOperations(context.Background(), test.query)\n\n\t\t\tif test.expectError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Len(t, test.conn.recordedQueries, 1)\n\t\t\t\tverifyQuerySnapshot(t, test.conn.recordedQueries...)\n\t\t\t\trequire.Equal(t, test.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFindTraces_Success(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdata []*dbmodel.SpanRow\n\t}{\n\t\t{\n\t\t\tname: \"single span\",\n\t\t\tdata: singleSpan,\n\t\t},\n\t\t{\n\t\t\tname: \"multiple spans\",\n\t\t\tdata: multipleSpans,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconn := &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansQuery: {\n\t\t\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\t\t\tdata:   tt.data,\n\t\t\t\t\t\t\tscanFn: scanSpanRowFn(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\treader := NewReader(conn, testReaderConfig)\n\t\t\tfindTracesIter := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\t\t\tAttributes: pcommon.NewMap(),\n\t\t\t})\n\t\t\ttraces, err := jiter.FlattenWithErrors(findTracesIter)\n\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, conn.recordedQueries, 1)\n\t\t\tverifyQuerySnapshot(t, conn.recordedQueries...)\n\t\t\trequireTracesEqual(t, tt.data, traces)\n\t\t})\n\t}\n}\n\nfunc TestFindTraces_WithFilters(t *testing.T) {\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\trows: &testRows[dbmodel.AttributeMetadata]{\n\t\t\t\t\tdata:   testAttributeMetadata,\n\t\t\t\t\tscanFn: scanAttributeMetadataFn(),\n\t\t\t\t},\n\t\t\t},\n\t\t\tsql.SelectSpansQuery: {\n\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\tdata:   multipleSpans,\n\t\t\t\t\tscanFn: scanSpanRowFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tattributes := buildTestAttributes()\n\n\titer := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tServiceName:   \"serviceA\",\n\t\tOperationName: \"operationA\",\n\t\tDurationMin:   1 * time.Nanosecond,\n\t\tDurationMax:   1 * time.Second,\n\t\tStartTimeMin:  now.Add(-1 * time.Hour),\n\t\tStartTimeMax:  now,\n\t\tAttributes:    attributes,\n\t\tSearchDepth:   5,\n\t})\n\ttraces, err := jiter.FlattenWithErrors(iter)\n\trequire.NoError(t, err)\n\trequire.Len(t, conn.recordedQueries, 2)\n\tverifyQuerySnapshot(t, conn.recordedQueries...)\n\trequireTracesEqual(t, multipleSpans, traces)\n}\n\nfunc TestFindTraces_SearchDepthExceedsMax(t *testing.T) {\n\tdriver := &testDriver{\n\t\tt: t,\n\t}\n\treader := NewReader(driver, testReaderConfig)\n\titer := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tSearchDepth: 10000,\n\t\tAttributes:  pcommon.NewMap(),\n\t})\n\t_, err := jiter.FlattenWithErrors(iter)\n\trequire.ErrorContains(t, err, \"search depth 10000 exceeds maximum allowed 1000\")\n}\n\nfunc TestFindTraces_YieldFalseOnSuccessStopsIteration(t *testing.T) {\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectSpansQuery: {\n\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\tdata:   multipleSpans,\n\t\t\t\t\tscanFn: scanSpanRowFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tfindTracesIter := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t})\n\n\tvar gotTraces []ptrace.Traces\n\tfindTracesIter(func(traces []ptrace.Traces, err error) bool {\n\t\trequire.NoError(t, err)\n\t\tgotTraces = append(gotTraces, traces...)\n\t\treturn false // stop iteration after the first span\n\t})\n\n\trequire.Len(t, gotTraces, 1)\n\trequireTracesEqual(t, multipleSpans[0:1], gotTraces)\n}\n\nfunc TestFindTraces_ScanErrorContinues(t *testing.T) {\n\tscanCalled := 0\n\n\tscanFn := func(dest any, src *dbmodel.SpanRow) error {\n\t\tscanCalled++\n\t\tif scanCalled == 1 {\n\t\t\treturn assert.AnError // simulate scan error on the first row\n\t\t}\n\t\treturn scanSpanRowFn()(dest, src)\n\t}\n\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectSpansQuery: {\n\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\tdata:   multipleSpans,\n\t\t\t\t\tscanFn: scanFn,\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tfindTracesIter := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t})\n\n\texpected := multipleSpans[1:] // skip the first span which caused the error\n\tfor trace, err := range findTracesIter {\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, assert.AnError)\n\t\t\tcontinue\n\t\t}\n\t\trequireTracesEqual(t, expected, trace)\n\t}\n}\n\nfunc TestFindTraces_ErrorCases(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdriver      *testDriver\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"QueryError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansQuery: {\n\t\t\t\t\t\trows: nil,\n\t\t\t\t\t\terr:  assert.AnError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to query traces\",\n\t\t},\n\t\t{\n\t\t\tname: \"ScanError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SelectSpansQuery: {\n\t\t\t\t\t\trows: &testRows[*dbmodel.SpanRow]{\n\t\t\t\t\t\t\tdata:    singleSpan,\n\t\t\t\t\t\t\tscanErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to scan span row\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := NewReader(test.driver, testReaderConfig)\n\t\t\titer := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\t\t\tAttributes: pcommon.NewMap(),\n\t\t\t})\n\t\t\t_, err := jiter.FlattenWithErrors(iter)\n\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t})\n\t}\n}\n\nfunc TestFindTraces_BuildQueryError(t *testing.T) {\n\torig := marshalValueForQuery\n\tt.Cleanup(func() { marshalValueForQuery = orig })\n\n\tmarshalValueForQuery = func(pcommon.Value) (string, error) {\n\t\treturn \"\", assert.AnError\n\t}\n\n\tattrs := pcommon.NewMap()\n\tattrs.PutEmptySlice(\"bad_slice\").AppendEmpty()\n\n\treader := NewReader(&testDriver{t: t}, testReaderConfig)\n\titer := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes:  attrs,\n\t\tSearchDepth: 1,\n\t})\n\t_, err := jiter.FlattenWithErrors(iter)\n\trequire.ErrorContains(t, err, \"failed to build query\")\n}\n\nfunc TestFindTraceIDs(t *testing.T) {\n\tdriver := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SelectAttributeMetadata: {\n\t\t\t\trows: &testRows[dbmodel.AttributeMetadata]{\n\t\t\t\t\tdata:   testAttributeMetadata,\n\t\t\t\t\tscanFn: scanAttributeMetadataFn(),\n\t\t\t\t},\n\t\t\t},\n\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\tdata:   testTraceIDsData,\n\t\t\t\t\tscanFn: scanTraceIDFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\treader := NewReader(driver, testReaderConfig)\n\tattributes := buildTestAttributes()\n\n\titer := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tServiceName:   \"serviceA\",\n\t\tOperationName: \"operationA\",\n\t\tDurationMin:   1 * time.Nanosecond,\n\t\tDurationMax:   1 * time.Second,\n\t\tStartTimeMin:  now.Add(-1 * time.Hour),\n\t\tStartTimeMax:  now,\n\t\tAttributes:    attributes,\n\t\tSearchDepth:   5,\n\t})\n\tids, err := jiter.FlattenWithErrors(iter)\n\trequire.NoError(t, err)\n\trequire.Len(t, driver.recordedQueries, 2)\n\tverifyQuerySnapshot(t, driver.recordedQueries...)\n\trequire.Equal(t, []tracestore.FoundTraceID{\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}),\n\t\t\tStart:   now.Add(-1 * time.Hour),\n\t\t\tEnd:     now,\n\t\t},\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}),\n\t\t},\n\t}, ids)\n}\n\nfunc TestFindTraceIDs_SearchDepthExceedsMax(t *testing.T) {\n\tdriver := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\tdata: [][]any{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"00000000000000000000000000000001\",\n\t\t\t\t\t\t\ttime.Now().Add(-1 * time.Hour),\n\t\t\t\t\t\t\ttime.Now().Add(-1 * time.Minute),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"00000000000000000000000000000002\",\n\t\t\t\t\t\t\ttime.Now().Add(-2 * time.Hour),\n\t\t\t\t\t\t\ttime.Now().Add(-2 * time.Minute),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tscanFn: scanTraceIDFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\treader := NewReader(driver, testReaderConfig)\n\titer := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tSearchDepth: 10000,\n\t})\n\t_, err := jiter.FlattenWithErrors(iter)\n\trequire.ErrorContains(t, err, \"search depth 10000 exceeds maximum allowed 1000\")\n}\n\nfunc TestFindTraceIDs_YieldFalseOnSuccessStopsIteration(t *testing.T) {\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\tdata:   testTraceIDsData,\n\t\t\t\t\tscanFn: scanTraceIDFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tfindTraceIDsIter := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t})\n\n\tvar gotTraceIDs []tracestore.FoundTraceID\n\tfindTraceIDsIter(func(traceIDs []tracestore.FoundTraceID, err error) bool {\n\t\trequire.NoError(t, err)\n\t\tgotTraceIDs = append(gotTraceIDs, traceIDs...)\n\t\treturn false // stop iteration after the first trace ID\n\t})\n\n\trequire.Len(t, gotTraceIDs, 1)\n\trequire.Equal(t, []tracestore.FoundTraceID{\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}),\n\t\t\tStart:   now.Add(-1 * time.Hour),\n\t\t\tEnd:     now,\n\t\t},\n\t}, gotTraceIDs)\n}\n\nfunc TestFindTraceIDs_ScanErrorContinues(t *testing.T) {\n\tscanCalled := 0\n\n\tscanFn := func(dest any, src []any) error {\n\t\tscanCalled++\n\t\tif scanCalled == 1 {\n\t\t\treturn assert.AnError // simulate scan error on the first row\n\t\t}\n\t\treturn scanTraceIDFn()(dest, src)\n\t}\n\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\tdata:   testTraceIDsData,\n\t\t\t\t\tscanFn: scanFn,\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, testReaderConfig)\n\tfindTraceIDsIter := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t})\n\n\texpected := []tracestore.FoundTraceID{\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}),\n\t\t},\n\t}\n\n\tfor traceID, err := range findTraceIDsIter {\n\t\tif err != nil {\n\t\t\trequire.ErrorIs(t, err, assert.AnError)\n\t\t\tcontinue\n\t\t}\n\t\trequire.Equal(t, expected, traceID)\n\t}\n}\n\nfunc TestFindTraceIDs_DecodeErrorContinues(t *testing.T) {\n\tconn := &testDriver{\n\t\tt: t,\n\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\tdata: [][]any{\n\t\t\t\t\t\ttestTraceIDsData[0],\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"0x\",\n\t\t\t\t\t\t\ttime.Now().Add(-2 * time.Hour),\n\t\t\t\t\t\t\ttime.Now().Add(-2 * time.Minute),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"invalid\",\n\t\t\t\t\t\t\ttime.Now().Add(-3 * time.Hour),\n\t\t\t\t\t\t\ttime.Now().Add(-3 * time.Minute),\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttestTraceIDsData[1],\n\t\t\t\t\t},\n\t\t\t\t\tscanFn: scanTraceIDFn(),\n\t\t\t\t},\n\t\t\t\terr: nil,\n\t\t\t},\n\t\t},\n\t}\n\n\treader := NewReader(conn, ReaderConfig{})\n\tfindTraceIDsIter := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t})\n\n\texpectedValidTraceIDs := []tracestore.FoundTraceID{\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}),\n\t\t\tStart:   now.Add(-1 * time.Hour),\n\t\t\tEnd:     now,\n\t\t},\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}),\n\t\t},\n\t}\n\n\tvar gotTraceIDs []tracestore.FoundTraceID\n\tvar errorCount int\n\n\tfor traceID, err := range findTraceIDsIter {\n\t\tif err != nil {\n\t\t\trequire.ErrorContains(t, err, \"failed to decode trace ID\")\n\t\t\terrorCount++\n\t\t\tcontinue\n\t\t}\n\t\tgotTraceIDs = append(gotTraceIDs, traceID...)\n\t}\n\n\trequire.Equal(t, 2, errorCount)\n\trequire.Equal(t, expectedValidTraceIDs, gotTraceIDs)\n}\n\nfunc TestFindTraceIDs_ErrorCases(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdriver      *testDriver\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"QueryError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\t\t\trows: nil,\n\t\t\t\t\t\terr:  assert.AnError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to query trace IDs\",\n\t\t},\n\t\t{\n\t\t\tname: \"ScanError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\t\t\tdata:    testTraceIDsData,\n\t\t\t\t\t\t\tscanErr: assert.AnError,\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to scan row\",\n\t\t},\n\t\t{\n\t\t\tname: \"DecodeError\",\n\t\t\tdriver: &testDriver{\n\t\t\t\tt: t,\n\t\t\t\tqueryResponses: map[string]*testQueryResponse{\n\t\t\t\t\tsql.SearchTraceIDsBase: {\n\t\t\t\t\t\trows: &testRows[[]any]{\n\t\t\t\t\t\t\tdata: [][]any{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"0x\",\n\t\t\t\t\t\t\t\t\ttime.Now().Add(-1 * time.Hour),\n\t\t\t\t\t\t\t\t\ttime.Now().Add(-1 * time.Minute),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tscanFn: scanTraceIDFn(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"failed to decode trace ID\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := NewReader(test.driver, ReaderConfig{})\n\t\t\titer := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\t\t\tAttributes: pcommon.NewMap(),\n\t\t\t})\n\t\t\t_, err := jiter.FlattenWithErrors(iter)\n\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t})\n\t}\n}\n\nfunc TestFindTraceIDs_BuildQueryError(t *testing.T) {\n\torig := marshalValueForQuery\n\tt.Cleanup(func() { marshalValueForQuery = orig })\n\n\tmarshalValueForQuery = func(pcommon.Value) (string, error) {\n\t\treturn \"\", assert.AnError\n\t}\n\n\tattrs := pcommon.NewMap()\n\tattrs.PutEmptyMap(\"bad_map\").PutEmpty(\"key\")\n\n\treader := NewReader(&testDriver{t: t}, testReaderConfig)\n\titer := reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes:  attrs,\n\t\tSearchDepth: 1,\n\t})\n\t_, err := jiter.FlattenWithErrors(iter)\n\trequire.ErrorContains(t, err, \"failed to build query\")\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestFindTraceIDs_1.sql",
    "content": "SELECT\n    attribute_key,\n    type,\n    level\nFROM\n    attribute_metadata\nWHERE\n\tattribute_key IN (?, ?, ?, ?, ?, ?, ?, ?, ?)\nGROUP BY\n\tattribute_key,\n\ttype,\n\tlevel\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestFindTraceIDs_2.sql",
    "content": "SELECT\n    l.trace_id,\n    min(t.start) AS start,\n    max(t.end) AS end\nFROM (\n\tSELECT DISTINCT\n\t    s.trace_id\n\tFROM spans s\n\tWHERE 1=1\n\t\tAND s.service_name = ?\n\t\tAND s.name = ?\n\t\tAND s.duration >= ?\n\t\tAND s.duration <= ?\n\t\tAND s.start_time >= ?\n\t\tAND s.start_time <= ?\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_bool_attributes.key, s.resource_bool_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.double_attributes.key, s.double_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.int_attributes.key, s.int_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_int_attributes.key, s.resource_int_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_str_attributes.key, s.resource_str_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.scope_str_attributes.key, s.scope_str_attributes.value)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)\n\t\t\tOR \n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.links)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.scope_int_attributes.key, s.scope_int_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t)\n\t\tAND (\n\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)\n\t\t)\n\tLIMIT ?\n) l\nLEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id\nGROUP BY l.trace_id\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestFindTraces_Success/multiple_spans_1.sql",
    "content": "SELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\nWHERE s.trace_id IN (\n\tSELECT trace_id FROM (\n\t\tSELECT\n\t\t    l.trace_id,\n\t\t    min(t.start) AS start,\n\t\t    max(t.end) AS end\n\t\tFROM (\n\t\t\tSELECT DISTINCT\n\t\t\t    s.trace_id\n\t\t\tFROM spans s\n\t\t\tWHERE 1=1\n\t\t\tLIMIT ?\n\t\t) l\n\t\tLEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id\n\t\tGROUP BY l.trace_id\n\t)\n)\nORDER BY s.trace_id\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestFindTraces_Success/single_span_1.sql",
    "content": "SELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\nWHERE s.trace_id IN (\n\tSELECT trace_id FROM (\n\t\tSELECT\n\t\t    l.trace_id,\n\t\t    min(t.start) AS start,\n\t\t    max(t.end) AS end\n\t\tFROM (\n\t\t\tSELECT DISTINCT\n\t\t\t    s.trace_id\n\t\t\tFROM spans s\n\t\t\tWHERE 1=1\n\t\t\tLIMIT ?\n\t\t) l\n\t\tLEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id\n\t\tGROUP BY l.trace_id\n\t)\n)\nORDER BY s.trace_id\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestFindTraces_WithFilters_1.sql",
    "content": "SELECT\n    attribute_key,\n    type,\n    level\nFROM\n    attribute_metadata\nWHERE\n\tattribute_key IN (?, ?, ?, ?, ?, ?, ?, ?, ?)\nGROUP BY\n\tattribute_key,\n\ttype,\n\tlevel\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestFindTraces_WithFilters_2.sql",
    "content": "SELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\nWHERE s.trace_id IN (\n\tSELECT trace_id FROM (\n\t\tSELECT\n\t\t    l.trace_id,\n\t\t    min(t.start) AS start,\n\t\t    max(t.end) AS end\n\t\tFROM (\n\t\t\tSELECT DISTINCT\n\t\t\t    s.trace_id\n\t\t\tFROM spans s\n\t\t\tWHERE 1=1\n\t\t\t\tAND s.service_name = ?\n\t\t\t\tAND s.name = ?\n\t\t\t\tAND s.duration >= ?\n\t\t\t\tAND s.duration <= ?\n\t\t\t\tAND s.start_time >= ?\n\t\t\t\tAND s.start_time <= ?\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_bool_attributes.key, s.resource_bool_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.bool_attributes.key, x.bool_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.double_attributes.key, s.double_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.double_attributes.key, x.double_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.int_attributes.key, s.int_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_int_attributes.key, s.resource_int_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.int_attributes.key, x.int_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.complex_attributes.key, x.complex_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_str_attributes.key, s.resource_str_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.scope_str_attributes.key, s.scope_str_attributes.value)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)\n\t\t\t\t\tOR \n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.links)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.str_attributes.key, s.str_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.bool_attributes.key, s.bool_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_double_attributes.key, s.resource_double_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.scope_int_attributes.key, s.scope_int_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.resource_complex_attributes.key, s.resource_complex_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists((key, value) -> key = ? AND value = ?, s.complex_attributes.key, s.complex_attributes.value)\n\t\t\t\t)\n\t\t\t\tAND (\n\t\t\t\t\tarrayExists(x -> arrayExists((key, value) -> key = ? AND value = ?, x.str_attributes.key, x.str_attributes.value), s.events)\n\t\t\t\t)\n\t\t\tLIMIT ?\n\t\t) l\n\t\tLEFT JOIN trace_id_timestamps t ON l.trace_id = t.trace_id\n\t\tGROUP BY l.trace_id\n\t)\n)\nORDER BY s.trace_id\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestGetOperations/successfully_returns_operations_by_kind_1.sql",
    "content": "SELECT\n    name,\n    span_kind\nFROM\n    operations\nWHERE\n    service_name = ?\n    AND span_kind = ?\nGROUP BY name, span_kind\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestGetOperations/successfully_returns_operations_for_all_kinds_1.sql",
    "content": "SELECT\n    name,\n    span_kind\nFROM\n    operations\nWHERE\n    service_name = ?\nGROUP BY name, span_kind\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestGetServices/successfully_returns_services_1.sql",
    "content": "SELECT\n    name\nFROM\n    services\nGROUP BY name\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestGetTraces_Success/multiple_spans_1.sql",
    "content": "SELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\n WHERE s.trace_id = ?\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestGetTraces_Success/single_span_1.sql",
    "content": "SELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\n WHERE s.trace_id = ?\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestGetTraces_Success/with_time_range_1.sql",
    "content": "SELECT\n    id,\n    trace_id,\n    trace_state,\n    parent_span_id,\n    name,\n    kind,\n    start_time,\n    status_code,\n    status_message,\n    duration,\n    bool_attributes.key,\n    bool_attributes.value,\n    double_attributes.key,\n    double_attributes.value,\n    int_attributes.key,\n    int_attributes.value,\n    str_attributes.key,\n    str_attributes.value,\n    complex_attributes.key,\n    complex_attributes.value,\n    events.name,\n    events.timestamp,\n    events.bool_attributes.key,\n    events.bool_attributes.value,\n    events.double_attributes.key,\n    events.double_attributes.value,\n    events.int_attributes.key,\n    events.int_attributes.value,\n    events.str_attributes.key,\n    events.str_attributes.value,\n    events.complex_attributes.key,\n    events.complex_attributes.value,\n    links.trace_id,\n    links.span_id,\n    links.trace_state,\n    links.bool_attributes.key,\n    links.bool_attributes.value,\n    links.double_attributes.key,\n    links.double_attributes.value,\n    links.int_attributes.key,\n    links.int_attributes.value,\n    links.str_attributes.key,\n    links.str_attributes.value,\n    links.complex_attributes.key,\n    links.complex_attributes.value,\n    service_name,\n    resource_bool_attributes.key,\n    resource_bool_attributes.value,\n    resource_double_attributes.key,\n    resource_double_attributes.value,\n    resource_int_attributes.key,\n    resource_int_attributes.value,\n    resource_str_attributes.key,\n    resource_str_attributes.value,\n    resource_complex_attributes.key,\n    resource_complex_attributes.value,\n    scope_name,\n    scope_version,\n    scope_bool_attributes.key,\n    scope_bool_attributes.value,\n    scope_double_attributes.key,\n    scope_double_attributes.value,\n    scope_int_attributes.key,\n    scope_int_attributes.value,\n    scope_str_attributes.key,\n    scope_str_attributes.value,\n    scope_complex_attributes.key,\n    scope_complex_attributes.value\nFROM\n    spans s\n WHERE s.trace_id = ? AND s.start_time >= ? AND s.start_time <= ?\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/snapshots/TestWriter_Success_1.sql",
    "content": "INSERT INTO\n    spans (\n        id,\n        trace_id,\n        trace_state,\n        parent_span_id,\n        name,\n        kind,\n        start_time,\n        status_code,\n        status_message,\n        duration,\n        bool_attributes.key,\n        bool_attributes.value,\n        double_attributes.key,\n        double_attributes.value,\n        int_attributes.key,\n        int_attributes.value,\n        str_attributes.key,\n        str_attributes.value,\n        complex_attributes.key,\n        complex_attributes.value,\n        events.name,\n        events.timestamp,\n        events.bool_attributes,\n        events.double_attributes,\n        events.int_attributes,\n        events.str_attributes,\n        events.complex_attributes,\n        links.trace_id,\n        links.span_id,\n        links.trace_state,\n        links.bool_attributes,\n        links.double_attributes,\n        links.int_attributes,\n        links.str_attributes,\n        links.complex_attributes,\n        service_name,\n        resource_bool_attributes.key,\n        resource_bool_attributes.value,\n        resource_double_attributes.key,\n        resource_double_attributes.value,\n        resource_int_attributes.key,\n        resource_int_attributes.value,\n        resource_str_attributes.key,\n        resource_str_attributes.value,\n        resource_complex_attributes.key,\n        resource_complex_attributes.value,\n        scope_name,\n        scope_version,\n        scope_bool_attributes.key,\n        scope_bool_attributes.value,\n        scope_double_attributes.key,\n        scope_double_attributes.value,\n        scope_int_attributes.key,\n        scope_int_attributes.value,\n        scope_str_attributes.key,\n        scope_str_attributes.value,\n        scope_complex_attributes.key,\n        scope_complex_attributes.value\n    )\nVALUES\n    (\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?,\n        ?\n    )\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/spans_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\nvar traceID = pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})\n\nvar (\n\ttraceIDHex1 = \"00000000000000000000000000000001\"\n\ttraceIDHex2 = \"00000000000000000000000000000002\"\n)\n\nvar now = time.Date(2025, 6, 14, 10, 0, 0, 0, time.UTC)\n\nvar singleSpan = []*dbmodel.SpanRow{\n\t{\n\t\tID:            \"0000000000000001\",\n\t\tTraceID:       traceID.String(),\n\t\tTraceState:    \"state1\",\n\t\tName:          \"GET /api/user\",\n\t\tKind:          \"server\",\n\t\tStartTime:     now,\n\t\tStatusCode:    \"Ok\",\n\t\tStatusMessage: \"success\",\n\t\tDuration:      1_000_000_000,\n\t\tAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"authenticated\", \"cache_hit\"},\n\t\t\tBoolValues:   []bool{true, false},\n\t\t\tDoubleKeys:   []string{\"response_time\", \"cpu_usage\"},\n\t\t\tDoubleValues: []float64{0.123, 45.67},\n\t\t\tIntKeys:      []string{\"user_id\", \"request_size\"},\n\t\t\tIntValues:    []int64{12345, 1024},\n\t\t\tStrKeys:      []string{\"http.method\", \"http.url\"},\n\t\t\tStrValues:    []string{\"GET\", \"/api/user\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@request_body\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJuYW1lIjoidGVzdCJ9\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t\tEventNames:      []string{\"login\"},\n\t\tEventTimestamps: []time.Time{now},\n\t\tEventAttributes: dbmodel.Attributes2D{\n\t\t\tBoolKeys:     [][]string{{\"event.authenticated\", \"event.cached\"}},\n\t\t\tBoolValues:   [][]bool{{true, false}},\n\t\t\tDoubleKeys:   [][]string{{\"event.response_time\"}},\n\t\t\tDoubleValues: [][]float64{{0.001}},\n\t\t\tIntKeys:      [][]string{{\"event.sequence\"}},\n\t\t\tIntValues:    [][]int64{{1}},\n\t\t\tStrKeys:      [][]string{{\"event.message\"}},\n\t\t\tStrValues:    [][]string{{\"user login successful\"}},\n\t\t\tComplexKeys: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@event.payload\",\n\t\t\t\t\t\"@map@metadata\",\n\t\t\t\t\t\"@slice@tags\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tComplexValues: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"eyJ1c2VyX2lkIjoxMjM0NX0=\",\n\t\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tLinkTraceIDs:    []string{\"00000000000000000000000000000002\"},\n\t\tLinkSpanIDs:     []string{\"0000000000000002\"},\n\t\tLinkTraceStates: []string{\"state2\"},\n\t\tLinkAttributes: dbmodel.Attributes2D{\n\t\t\tBoolKeys:     [][]string{{\"link.validated\", \"link.active\"}},\n\t\t\tBoolValues:   [][]bool{{true, true}},\n\t\t\tDoubleKeys:   [][]string{{\"link.weight\"}},\n\t\t\tDoubleValues: [][]float64{{0.8}},\n\t\t\tIntKeys:      [][]string{{\"link.priority\"}},\n\t\t\tIntValues:    [][]int64{{1}},\n\t\t\tStrKeys:      [][]string{{\"link.type\"}},\n\t\t\tStrValues:    [][]string{{\"follows_from\"}},\n\t\t\tComplexKeys: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@link.metadata\",\n\t\t\t\t\t\"@map@metadata\",\n\t\t\t\t\t\"@slice@tags\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tComplexValues: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"eyJsaW5rX2lkIjoxfQ==\",\n\t\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tServiceName: \"user-service\",\n\t\tResourceAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"resource.available\", \"resource.active\"},\n\t\t\tBoolValues:   []bool{true, true},\n\t\t\tDoubleKeys:   []string{\"resource.cpu_limit\", \"resource.memory_usage\"},\n\t\t\tDoubleValues: []float64{2.5, 80.5},\n\t\t\tIntKeys:      []string{\"resource.instance_id\", \"resource.port\"},\n\t\t\tIntValues:    []int64{12345, 8080},\n\t\t\tStrKeys:      []string{\"service.name\", \"resource.host\", \"resource.region\"},\n\t\t\tStrValues:    []string{\"user-service\", \"host-1\", \"us-west-1\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@resource.metadata\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJkZXBsb3ltZW50IjoicHJvZCJ9\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t\tScopeName:    \"auth-scope\",\n\t\tScopeVersion: \"v1.0.0\",\n\t\tScopeAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"scope.enabled\", \"scope.persistent\"},\n\t\t\tBoolValues:   []bool{true, false},\n\t\t\tDoubleKeys:   []string{\"scope.version_number\", \"scope.priority\"},\n\t\t\tDoubleValues: []float64{1.0, 0.8},\n\t\t\tIntKeys:      []string{\"scope.instance_count\", \"scope.max_spans\"},\n\t\t\tIntValues:    []int64{5, 1000},\n\t\t\tStrKeys:      []string{\"scope.environment\", \"scope.component\"},\n\t\t\tStrValues:    []string{\"production\", \"auth\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@scope.metadata\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJzY29wZV90eXBlIjoiYXV0aGVudGljYXRpb24ifQ==\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t},\n}\n\nvar multipleSpans = []*dbmodel.SpanRow{\n\t{\n\t\tID:            \"0000000000000001\",\n\t\tTraceID:       traceID.String(),\n\t\tTraceState:    \"state1\",\n\t\tName:          \"GET /api/user\",\n\t\tKind:          \"server\",\n\t\tStartTime:     now,\n\t\tStatusCode:    \"Ok\",\n\t\tStatusMessage: \"success\",\n\t\tDuration:      1_000_000_000,\n\t\tAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"authenticated\", \"cache_hit\"},\n\t\t\tBoolValues:   []bool{true, false},\n\t\t\tDoubleKeys:   []string{\"response_time\", \"cpu_usage\"},\n\t\t\tDoubleValues: []float64{0.123, 45.67},\n\t\t\tIntKeys:      []string{\"user_id\", \"request_size\"},\n\t\t\tIntValues:    []int64{12345, 1024},\n\t\t\tStrKeys:      []string{\"http.method\", \"http.url\"},\n\t\t\tStrValues:    []string{\"GET\", \"/api/user\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@request_body\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJuYW1lIjoidGVzdCJ9\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t\tEventNames:      []string{\"login\"},\n\t\tEventTimestamps: []time.Time{now},\n\t\tEventAttributes: dbmodel.Attributes2D{\n\t\t\tBoolKeys:     [][]string{{\"event.authenticated\", \"event.cached\"}},\n\t\t\tBoolValues:   [][]bool{{true, false}},\n\t\t\tDoubleKeys:   [][]string{{\"event.response_time\"}},\n\t\t\tDoubleValues: [][]float64{{0.001}},\n\t\t\tIntKeys:      [][]string{{\"event.sequence\"}},\n\t\t\tIntValues:    [][]int64{{1}},\n\t\t\tStrKeys:      [][]string{{\"event.message\"}},\n\t\t\tStrValues:    [][]string{{\"user login successful\"}},\n\t\t\tComplexKeys: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@event.payload\",\n\t\t\t\t\t\"@map@metadata\",\n\t\t\t\t\t\"@slice@tags\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tComplexValues: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"eyJ1c2VyX2lkIjoxMjM0NX0=\",\n\t\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tLinkTraceIDs:    []string{\"00000000000000000000000000000002\"},\n\t\tLinkSpanIDs:     []string{\"0000000000000002\"},\n\t\tLinkTraceStates: []string{\"state2\"},\n\t\tLinkAttributes: dbmodel.Attributes2D{\n\t\t\tBoolKeys:     [][]string{{\"link.validated\", \"link.active\"}},\n\t\t\tBoolValues:   [][]bool{{true, true}},\n\t\t\tDoubleKeys:   [][]string{{\"link.weight\"}},\n\t\t\tDoubleValues: [][]float64{{0.8}},\n\t\t\tIntKeys:      [][]string{{\"link.priority\"}},\n\t\t\tIntValues:    [][]int64{{1}},\n\t\t\tStrKeys:      [][]string{{\"link.type\"}},\n\t\t\tStrValues:    [][]string{{\"follows_from\"}},\n\t\t\tComplexKeys: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@link.metadata\",\n\t\t\t\t\t\"@map@metadata\",\n\t\t\t\t\t\"@slice@tags\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tComplexValues: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"eyJsaW5rX2lkIjoxfQ==\",\n\t\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tServiceName: \"user-service\",\n\t\tResourceAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"resource.available\", \"resource.active\"},\n\t\t\tBoolValues:   []bool{true, true},\n\t\t\tDoubleKeys:   []string{\"resource.cpu_limit\", \"resource.memory_usage\"},\n\t\t\tDoubleValues: []float64{2.5, 80.5},\n\t\t\tIntKeys:      []string{\"resource.instance_id\", \"resource.port\"},\n\t\t\tIntValues:    []int64{12345, 8080},\n\t\t\tStrKeys:      []string{\"service.name\", \"resource.host\", \"resource.region\"},\n\t\t\tStrValues:    []string{\"user-service\", \"host-1\", \"us-west-1\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@resource.metadata\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJkZXBsb3ltZW50IjoicHJvZCJ9\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t\tScopeName:    \"auth-scope\",\n\t\tScopeVersion: \"v1.0.0\",\n\t\tScopeAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"scope.enabled\", \"scope.persistent\"},\n\t\t\tBoolValues:   []bool{true, false},\n\t\t\tDoubleKeys:   []string{\"scope.version_number\", \"scope.priority\"},\n\t\t\tDoubleValues: []float64{1.0, 0.8},\n\t\t\tIntKeys:      []string{\"scope.instance_count\", \"scope.max_spans\"},\n\t\t\tIntValues:    []int64{5, 1000},\n\t\t\tStrKeys:      []string{\"scope.environment\", \"scope.component\"},\n\t\t\tStrValues:    []string{\"production\", \"auth\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@scope.metadata\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJzY29wZV90eXBlIjoiYXV0aGVudGljYXRpb24ifQ==\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:            \"0000000000000003\",\n\t\tTraceID:       traceID.String(),\n\t\tTraceState:    \"state1\",\n\t\tParentSpanID:  \"0000000000000001\",\n\t\tName:          \"SELECT /db/query\",\n\t\tKind:          \"client\",\n\t\tStartTime:     now.Add(10 * time.Millisecond),\n\t\tStatusCode:    \"Ok\",\n\t\tStatusMessage: \"success\",\n\t\tDuration:      500_000_000,\n\t\tAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"db.cached\", \"db.readonly\"},\n\t\t\tBoolValues:   []bool{false, true},\n\t\t\tDoubleKeys:   []string{\"db.latency\", \"db.connections\"},\n\t\t\tDoubleValues: []float64{0.05, 5.0},\n\t\t\tIntKeys:      []string{\"db.rows_affected\", \"db.connection_id\"},\n\t\t\tIntValues:    []int64{150, 42},\n\t\t\tStrKeys:      []string{\"db.statement\", \"db.name\"},\n\t\t\tStrValues:    []string{\"SELECT * FROM users\", \"userdb\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@db.query_plan\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"UExBTiBTRUxFQ1Q=\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t\tEventNames:      []string{\"query-start\", \"query-end\"},\n\t\tEventTimestamps: []time.Time{now.Add(10 * time.Millisecond), now.Add(510 * time.Millisecond)},\n\t\tEventAttributes: dbmodel.Attributes2D{\n\t\t\tBoolKeys:     [][]string{{\"db.optimized\", \"db.indexed\"}, {\"db.cached\", \"db.successful\"}},\n\t\t\tBoolValues:   [][]bool{{true, false}, {true, false}},\n\t\t\tDoubleKeys:   [][]string{{\"db.query_time\"}, {\"db.result_time\"}},\n\t\t\tDoubleValues: [][]float64{{0.001}, {0.5}},\n\t\t\tIntKeys:      [][]string{{\"db.connection_pool_size\"}, {\"db.result_count\"}},\n\t\t\tIntValues:    [][]int64{{10}, {150}},\n\t\t\tStrKeys:      [][]string{{\"db.event.type\"}, {\"db.event.status\"}},\n\t\t\tStrValues:    [][]string{{\"query_execution_start\"}, {\"query_execution_complete\"}},\n\t\t\tComplexKeys: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@db.query_metadata\",\n\t\t\t\t\t\"@map@metadata\",\n\t\t\t\t\t\"@slice@tags\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@db.result_metadata\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tComplexValues: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"eyJxdWVyeV9pZCI6MTIzfQ==\",\n\t\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"eyJyb3dfY291bnQiOjE1MH0=\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tLinkTraceIDs:    []string{\"00000000000000000000000000000004\"},\n\t\tLinkSpanIDs:     []string{\"0000000000000004\"},\n\t\tLinkTraceStates: []string{\"state3\"},\n\t\tLinkAttributes: dbmodel.Attributes2D{\n\t\t\tBoolKeys:     [][]string{{\"link.persistent\", \"link.direct\"}},\n\t\t\tBoolValues:   [][]bool{{true, false}},\n\t\t\tDoubleKeys:   [][]string{{\"link.confidence\"}},\n\t\t\tDoubleValues: [][]float64{{0.95}},\n\t\t\tIntKeys:      [][]string{{\"link.sequence\"}},\n\t\t\tIntValues:    [][]int64{{2}},\n\t\t\tStrKeys:      [][]string{{\"link.operation\"}},\n\t\t\tStrValues:    [][]string{{\"child_of\"}},\n\t\t\tComplexKeys: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"@bytes@link.context\",\n\t\t\t\t\t\"@map@metadata\",\n\t\t\t\t\t\"@slice@tags\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tComplexValues: [][]string{\n\t\t\t\t{\n\t\t\t\t\t\"eyJkYl9jb250ZXh0IjoidXNlcmRiIn0=\",\n\t\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tServiceName: \"db-service\",\n\t\tResourceAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"resource.persistent\", \"resource.pooled\"},\n\t\t\tBoolValues:   []bool{true, true},\n\t\t\tDoubleKeys:   []string{\"resource.cpu_limit\", \"resource.memory_limit\"},\n\t\t\tDoubleValues: []float64{1.5, 512.0},\n\t\t\tIntKeys:      []string{\"resource.instance_id\", \"resource.max_connections\"},\n\t\t\tIntValues:    []int64{67890, 100},\n\t\t\tStrKeys:      []string{\"service.name\", \"resource.host\", \"resource.database_type\"},\n\t\t\tStrValues:    []string{\"db-service\", \"db-host-1\", \"postgresql\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@resource.config\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJkYl90eXBlIjoicG9zdGdyZXNxbCJ9\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t\tScopeName:    \"db-scope\",\n\t\tScopeVersion: \"v1.0.0\",\n\t\tScopeAttributes: dbmodel.Attributes{\n\t\t\tBoolKeys:     []string{\"scope.enabled\", \"scope.persistent\"},\n\t\t\tBoolValues:   []bool{true, false},\n\t\t\tDoubleKeys:   []string{\"scope.version_number\", \"scope.priority\"},\n\t\t\tDoubleValues: []float64{1.0, 0.8},\n\t\t\tIntKeys:      []string{\"scope.instance_count\", \"scope.max_spans\"},\n\t\t\tIntValues:    []int64{5, 1000},\n\t\t\tStrKeys:      []string{\"scope.environment\", \"scope.component\"},\n\t\t\tStrValues:    []string{\"production\", \"database\"},\n\t\t\tComplexKeys: []string{\n\t\t\t\t\"@bytes@scope.metadata\",\n\t\t\t\t\"@map@metadata\",\n\t\t\t\t\"@slice@tags\",\n\t\t\t},\n\t\t\tComplexValues: []string{\n\t\t\t\t\"eyJzY29wZV90eXBlIjoiZGF0YWJhc2UifQ==\",\n\t\t\t\t\"{\\\"kvlistValue\\\":{\\\"values\\\":[{\\\"key\\\":\\\"key\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"value\\\"}}]}}\",\n\t\t\t\t\"{\\\"arrayValue\\\":{\\\"values\\\":[{\\\"intValue\\\":\\\"1\\\"},{\\\"intValue\\\":\\\"2\\\"},{\\\"intValue\\\":\\\"3\\\"}]}}\",\n\t\t\t},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/writer.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/ClickHouse/clickhouse-go/v2/lib/driver\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\ntype Writer struct {\n\tconn driver.Conn\n}\n\n// NewWriter returns a new Writer instance that uses the given ClickHouse connection\n// to write trace data.\n//\n// The provided connection is used for writing traces.\n// This connection should not have instrumentation enabled to avoid recursively generating traces.\nfunc NewWriter(conn driver.Conn) *Writer {\n\treturn &Writer{conn: conn}\n}\n\nfunc (w *Writer) WriteTraces(ctx context.Context, td ptrace.Traces) error {\n\tbatch, err := w.conn.PrepareBatch(ctx, sql.InsertSpan)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to prepare batch: %w\", err)\n\t}\n\tdefer batch.Close()\n\tfor _, rs := range td.ResourceSpans().All() {\n\t\tfor _, ss := range rs.ScopeSpans().All() {\n\t\t\tfor _, span := range ss.Spans().All() {\n\t\t\t\tsr := dbmodel.ToRow(rs.Resource(), ss.Scope(), span)\n\t\t\t\terr = batch.Append(\n\t\t\t\t\tsr.ID,\n\t\t\t\t\tsr.TraceID,\n\t\t\t\t\tsr.TraceState,\n\t\t\t\t\tsr.ParentSpanID,\n\t\t\t\t\tsr.Name,\n\t\t\t\t\tsr.Kind,\n\t\t\t\t\tsr.StartTime,\n\t\t\t\t\tsr.StatusCode,\n\t\t\t\t\tsr.StatusMessage,\n\t\t\t\t\tsr.Duration,\n\t\t\t\t\tsr.Attributes.BoolKeys,\n\t\t\t\t\tsr.Attributes.BoolValues,\n\t\t\t\t\tsr.Attributes.DoubleKeys,\n\t\t\t\t\tsr.Attributes.DoubleValues,\n\t\t\t\t\tsr.Attributes.IntKeys,\n\t\t\t\t\tsr.Attributes.IntValues,\n\t\t\t\t\tsr.Attributes.StrKeys,\n\t\t\t\t\tsr.Attributes.StrValues,\n\t\t\t\t\tsr.Attributes.ComplexKeys,\n\t\t\t\t\tsr.Attributes.ComplexValues,\n\t\t\t\t\tsr.EventNames,\n\t\t\t\t\tsr.EventTimestamps,\n\t\t\t\t\ttoTuple(sr.EventAttributes.BoolKeys, sr.EventAttributes.BoolValues),\n\t\t\t\t\ttoTuple(sr.EventAttributes.DoubleKeys, sr.EventAttributes.DoubleValues),\n\t\t\t\t\ttoTuple(sr.EventAttributes.IntKeys, sr.EventAttributes.IntValues),\n\t\t\t\t\ttoTuple(sr.EventAttributes.StrKeys, sr.EventAttributes.StrValues),\n\t\t\t\t\ttoTuple(sr.EventAttributes.ComplexKeys, sr.EventAttributes.ComplexValues),\n\t\t\t\t\tsr.LinkTraceIDs,\n\t\t\t\t\tsr.LinkSpanIDs,\n\t\t\t\t\tsr.LinkTraceStates,\n\t\t\t\t\ttoTuple(sr.LinkAttributes.BoolKeys, sr.LinkAttributes.BoolValues),\n\t\t\t\t\ttoTuple(sr.LinkAttributes.DoubleKeys, sr.LinkAttributes.DoubleValues),\n\t\t\t\t\ttoTuple(sr.LinkAttributes.IntKeys, sr.LinkAttributes.IntValues),\n\t\t\t\t\ttoTuple(sr.LinkAttributes.StrKeys, sr.LinkAttributes.StrValues),\n\t\t\t\t\ttoTuple(sr.LinkAttributes.ComplexKeys, sr.LinkAttributes.ComplexValues),\n\t\t\t\t\tsr.ServiceName,\n\t\t\t\t\tsr.ResourceAttributes.BoolKeys,\n\t\t\t\t\tsr.ResourceAttributes.BoolValues,\n\t\t\t\t\tsr.ResourceAttributes.DoubleKeys,\n\t\t\t\t\tsr.ResourceAttributes.DoubleValues,\n\t\t\t\t\tsr.ResourceAttributes.IntKeys,\n\t\t\t\t\tsr.ResourceAttributes.IntValues,\n\t\t\t\t\tsr.ResourceAttributes.StrKeys,\n\t\t\t\t\tsr.ResourceAttributes.StrValues,\n\t\t\t\t\tsr.ResourceAttributes.ComplexKeys,\n\t\t\t\t\tsr.ResourceAttributes.ComplexValues,\n\t\t\t\t\tsr.ScopeName,\n\t\t\t\t\tsr.ScopeVersion,\n\t\t\t\t\tsr.ScopeAttributes.BoolKeys,\n\t\t\t\t\tsr.ScopeAttributes.BoolValues,\n\t\t\t\t\tsr.ScopeAttributes.DoubleKeys,\n\t\t\t\t\tsr.ScopeAttributes.DoubleValues,\n\t\t\t\t\tsr.ScopeAttributes.IntKeys,\n\t\t\t\t\tsr.ScopeAttributes.IntValues,\n\t\t\t\t\tsr.ScopeAttributes.StrKeys,\n\t\t\t\t\tsr.ScopeAttributes.StrValues,\n\t\t\t\t\tsr.ScopeAttributes.ComplexKeys,\n\t\t\t\t\tsr.ScopeAttributes.ComplexValues,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to append span to batch: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif err := batch.Send(); err != nil {\n\t\treturn fmt.Errorf(\"failed to send batch: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc toTuple[T any](keys [][]string, values [][]T) [][][]any {\n\ttuple := make([][][]any, 0, len(keys))\n\tfor i := range keys {\n\t\tinner := make([][]any, 0, len(keys[i]))\n\t\tfor j := range keys[i] {\n\t\t\tinner = append(inner, []any{keys[i][j], values[i][j]})\n\t\t}\n\t\ttuple = append(tuple, inner)\n\t}\n\treturn tuple\n}\n"
  },
  {
    "path": "internal/storage/v2/clickhouse/tracestore/writer_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/sql\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/clickhouse/tracestore/dbmodel\"\n)\n\nfunc tracesFromSpanRows(rows []*dbmodel.SpanRow) ptrace.Traces {\n\ttd := ptrace.NewTraces()\n\trs := td.ResourceSpans()\n\tfor _, r := range rows {\n\t\ttrace := dbmodel.FromRow(r)\n\t\tsrcRS := trace.ResourceSpans()\n\t\tfor i := 0; i < srcRS.Len(); i++ {\n\t\t\tsrcRS.At(i).CopyTo(rs.AppendEmpty())\n\t\t}\n\t}\n\treturn td\n}\n\nfunc TestWriter_Success(t *testing.T) {\n\tb := &testBatch{t: t}\n\tconn := &testDriver{\n\t\tt: t,\n\t\tbatchResponses: map[string]*testBatchResponse{\n\t\t\tsql.InsertSpan: {\n\t\t\t\tbatch: b,\n\t\t\t},\n\t\t},\n\t}\n\tw := NewWriter(conn)\n\n\ttd := tracesFromSpanRows(multipleSpans)\n\n\terr := w.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\n\trequire.Len(t, conn.recordedQueries, 1)\n\tverifyQuerySnapshot(t, conn.recordedQueries[0])\n\trequire.True(t, b.sendCalled)\n\trequire.Len(t, b.appended, len(multipleSpans))\n\n\tfor i, expected := range multipleSpans {\n\t\trow := b.appended[i]\n\n\t\trequire.Equal(t, expected.ID, row[0])                        // SpanID\n\t\trequire.Equal(t, expected.TraceID, row[1])                   // TraceID\n\t\trequire.Equal(t, expected.TraceState, row[2])                // TraceState\n\t\trequire.Equal(t, expected.ParentSpanID, row[3])              // ParentSpanID\n\t\trequire.Equal(t, expected.Name, row[4])                      // Name\n\t\trequire.Equal(t, strings.ToLower(expected.Kind), row[5])     // Kind\n\t\trequire.Equal(t, expected.StartTime, row[6])                 // StartTimestamp\n\t\trequire.Equal(t, expected.StatusCode, row[7])                // Status code\n\t\trequire.Equal(t, expected.StatusMessage, row[8])             // Status message\n\t\trequire.EqualValues(t, expected.Duration, row[9])            // Duration\n\t\trequire.Equal(t, expected.Attributes.BoolKeys, row[10])      // Bool attribute keys\n\t\trequire.Equal(t, expected.Attributes.BoolValues, row[11])    // Bool attribute values\n\t\trequire.Equal(t, expected.Attributes.DoubleKeys, row[12])    // Double attribute keys\n\t\trequire.Equal(t, expected.Attributes.DoubleValues, row[13])  // Double attribute values\n\t\trequire.Equal(t, expected.Attributes.IntKeys, row[14])       // Int attribute keys\n\t\trequire.Equal(t, expected.Attributes.IntValues, row[15])     // Int attribute values\n\t\trequire.Equal(t, expected.Attributes.StrKeys, row[16])       // Str attribute keys\n\t\trequire.Equal(t, expected.Attributes.StrValues, row[17])     // Str attribute values\n\t\trequire.Equal(t, expected.Attributes.ComplexKeys, row[18])   // Complex attribute keys\n\t\trequire.Equal(t, expected.Attributes.ComplexValues, row[19]) // Complex attribute values\n\t\trequire.Equal(t, expected.EventNames, row[20])               // Event names\n\t\trequire.Equal(t, expected.EventTimestamps, row[21])          // Event timestamps\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.EventAttributes.BoolKeys, expected.EventAttributes.BoolValues),\n\t\t\trow[22],\n\t\t) // Event bool attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.EventAttributes.DoubleKeys, expected.EventAttributes.DoubleValues),\n\t\t\trow[23],\n\t\t) // Event double attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.EventAttributes.IntKeys, expected.EventAttributes.IntValues),\n\t\t\trow[24],\n\t\t) // Event int attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.EventAttributes.StrKeys, expected.EventAttributes.StrValues),\n\t\t\trow[25],\n\t\t) // Event str attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.EventAttributes.ComplexKeys, expected.EventAttributes.ComplexValues),\n\t\t\trow[26],\n\t\t) // Event complex attributes\n\t\trequire.Equal(t, expected.LinkTraceIDs, row[27])    // Link TraceIDs\n\t\trequire.Equal(t, expected.LinkSpanIDs, row[28])     // Link SpanIDs\n\t\trequire.Equal(t, expected.LinkTraceStates, row[29]) // Link TraceStates\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.LinkAttributes.BoolKeys, expected.LinkAttributes.BoolValues),\n\t\t\trow[30],\n\t\t) // Link bool attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.LinkAttributes.DoubleKeys, expected.LinkAttributes.DoubleValues),\n\t\t\trow[31],\n\t\t) // Link double attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.LinkAttributes.IntKeys, expected.LinkAttributes.IntValues),\n\t\t\trow[32],\n\t\t) // Link int attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.LinkAttributes.StrKeys, expected.LinkAttributes.StrValues),\n\t\t\trow[33],\n\t\t) // Link str attributes\n\t\trequire.Equal(t,\n\t\t\ttoTuple(expected.LinkAttributes.ComplexKeys, expected.LinkAttributes.ComplexValues),\n\t\t\trow[34],\n\t\t) // Link complex attributes\n\t\trequire.Equal(t, expected.ServiceName, row[35])                      // Service name\n\t\trequire.Equal(t, expected.ResourceAttributes.BoolKeys, row[36])      // Resource bool attribute keys\n\t\trequire.Equal(t, expected.ResourceAttributes.BoolValues, row[37])    // Resource bool attribute values\n\t\trequire.Equal(t, expected.ResourceAttributes.DoubleKeys, row[38])    // Resource double attribute keys\n\t\trequire.Equal(t, expected.ResourceAttributes.DoubleValues, row[39])  // Resource double attribute values\n\t\trequire.Equal(t, expected.ResourceAttributes.IntKeys, row[40])       // Resource int attribute keys\n\t\trequire.Equal(t, expected.ResourceAttributes.IntValues, row[41])     // Resource int attribute values\n\t\trequire.Equal(t, expected.ResourceAttributes.StrKeys, row[42])       // Resource str attribute keys\n\t\trequire.Equal(t, expected.ResourceAttributes.StrValues, row[43])     // Resource str attribute values\n\t\trequire.Equal(t, expected.ResourceAttributes.ComplexKeys, row[44])   // Resource complex attribute keys\n\t\trequire.Equal(t, expected.ResourceAttributes.ComplexValues, row[45]) // Resource complex attribute values\n\t\trequire.Equal(t, expected.ScopeName, row[46])                        // Scope name\n\t\trequire.Equal(t, expected.ScopeVersion, row[47])                     // Scope version\n\t\trequire.Equal(t, expected.ScopeAttributes.BoolKeys, row[48])         // Scope bool attribute keys\n\t\trequire.Equal(t, expected.ScopeAttributes.BoolValues, row[49])       // Scope bool attribute values\n\t\trequire.Equal(t, expected.ScopeAttributes.DoubleKeys, row[50])       // Scope double attribute keys\n\t\trequire.Equal(t, expected.ScopeAttributes.DoubleValues, row[51])     // Scope double attribute values\n\t\trequire.Equal(t, expected.ScopeAttributes.IntKeys, row[52])          // Scope int attribute keys\n\t\trequire.Equal(t, expected.ScopeAttributes.IntValues, row[53])        // Scope int attribute values\n\t\trequire.Equal(t, expected.ScopeAttributes.StrKeys, row[54])          // Scope str attribute keys\n\t\trequire.Equal(t, expected.ScopeAttributes.StrValues, row[55])        // Scope str attribute values\n\t\trequire.Equal(t, expected.ScopeAttributes.ComplexKeys, row[56])      // Scope complex attribute keys\n\t\trequire.Equal(t, expected.ScopeAttributes.ComplexValues, row[57])    // Scope complex attribute values\n\t}\n}\n\nfunc TestWriter_PrepareBatchError(t *testing.T) {\n\tconn := &testDriver{\n\t\tt: t,\n\t\tbatchResponses: map[string]*testBatchResponse{\n\t\t\tsql.InsertSpan: {\n\t\t\t\tbatch: nil,\n\t\t\t\terr:   assert.AnError,\n\t\t\t},\n\t\t},\n\t}\n\tw := NewWriter(conn)\n\terr := w.WriteTraces(context.Background(), tracesFromSpanRows(multipleSpans))\n\trequire.ErrorContains(t, err, \"failed to prepare batch\")\n\trequire.ErrorIs(t, err, assert.AnError)\n}\n\nfunc TestWriter_AppendBatchError(t *testing.T) {\n\tb := &testBatch{t: t, appendErr: assert.AnError}\n\tconn := &testDriver{\n\t\tt: t,\n\t\tbatchResponses: map[string]*testBatchResponse{\n\t\t\tsql.InsertSpan: {\n\t\t\t\tbatch: b,\n\t\t\t},\n\t\t},\n\t}\n\tw := NewWriter(conn)\n\terr := w.WriteTraces(context.Background(), tracesFromSpanRows(multipleSpans))\n\trequire.ErrorContains(t, err, \"failed to append span to batch\")\n\trequire.ErrorIs(t, err, assert.AnError)\n\trequire.False(t, b.sendCalled)\n}\n\nfunc TestWriter_SendError(t *testing.T) {\n\tb := &testBatch{t: t, sendErr: assert.AnError}\n\tconn := &testDriver{\n\t\tt: t,\n\t\tbatchResponses: map[string]*testBatchResponse{\n\t\t\tsql.InsertSpan: {\n\t\t\t\tbatch: b,\n\t\t\t},\n\t\t},\n\t}\n\tw := NewWriter(conn)\n\terr := w.WriteTraces(context.Background(), tracesFromSpanRows(multipleSpans))\n\trequire.ErrorContains(t, err, \"failed to send batch\")\n\trequire.ErrorIs(t, err, assert.AnError)\n\trequire.False(t, b.sendCalled)\n}\n\nfunc TestToTuple(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tkeys     [][]string\n\t\tvalues   [][]int\n\t\texpected [][][]any\n\t}{\n\t\t{\n\t\t\tname:     \"empty slices\",\n\t\t\tkeys:     [][]string{},\n\t\t\tvalues:   [][]int{},\n\t\t\texpected: [][][]any{},\n\t\t},\n\t\t{\n\t\t\tname:     \"single empty inner slice\",\n\t\t\tkeys:     [][]string{{}},\n\t\t\tvalues:   [][]int{{}},\n\t\t\texpected: [][][]any{{}},\n\t\t},\n\t\t{\n\t\t\tname:   \"single element\",\n\t\t\tkeys:   [][]string{{\"key1\"}},\n\t\t\tvalues: [][]int{{42}},\n\t\t\texpected: [][][]any{\n\t\t\t\t{\n\t\t\t\t\t{\"key1\", 42},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"multiple elements in single slice\",\n\t\t\tkeys:   [][]string{{\"key1\", \"key2\", \"key3\"}},\n\t\t\tvalues: [][]int{{10, 20, 30}},\n\t\t\texpected: [][][]any{\n\t\t\t\t{\n\t\t\t\t\t{\"key1\", 10},\n\t\t\t\t\t{\"key2\", 20},\n\t\t\t\t\t{\"key3\", 30},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"multiple slices with multiple elements\",\n\t\t\tkeys:   [][]string{{\"key1\", \"key2\"}, {\"key3\"}, {\"key4\", \"key5\", \"key6\"}},\n\t\t\tvalues: [][]int{{1, 2}, {3}, {4, 5, 6}},\n\t\t\texpected: [][][]any{\n\t\t\t\t{\n\t\t\t\t\t{\"key1\", 1},\n\t\t\t\t\t{\"key2\", 2},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{\"key3\", 3},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{\"key4\", 4},\n\t\t\t\t\t{\"key5\", 5},\n\t\t\t\t\t{\"key6\", 6},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := toTuple(tt.keys, tt.values)\n\t\t\trequire.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/dbmodel/converter.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport \"github.com/jaegertracing/jaeger-idl/model/v1\"\n\n// FromDomainDependencies converts model dependencies to database representation\nfunc FromDomainDependencies(dLinks []model.DependencyLink) []DependencyLink {\n\tif dLinks == nil {\n\t\treturn nil\n\t}\n\tret := make([]DependencyLink, len(dLinks))\n\tfor i, d := range dLinks {\n\t\tret[i] = DependencyLink{\n\t\t\tCallCount: d.CallCount,\n\t\t\tParent:    d.Parent,\n\t\t\tChild:     d.Child,\n\t\t}\n\t}\n\treturn ret\n}\n\n// ToDomainDependencies converts database representation of dependencies to model\nfunc ToDomainDependencies(dLinks []DependencyLink) []model.DependencyLink {\n\tif dLinks == nil {\n\t\treturn nil\n\t}\n\tret := make([]model.DependencyLink, len(dLinks))\n\tfor i, d := range dLinks {\n\t\tret[i] = model.DependencyLink{\n\t\t\tCallCount: d.CallCount,\n\t\t\tParent:    d.Parent,\n\t\t\tChild:     d.Child,\n\t\t}\n\t}\n\treturn ret\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/dbmodel/converter_test.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestConvertDependencies(t *testing.T) {\n\ttests := []struct {\n\t\tdLinks []model.DependencyLink\n\t}{\n\t\t{\n\t\t\tdLinks: []model.DependencyLink{{CallCount: 1, Parent: \"foo\", Child: \"bar\"}},\n\t\t},\n\t\t{\n\t\t\tdLinks: []model.DependencyLink{{CallCount: 3, Parent: \"foo\"}},\n\t\t},\n\t\t{\n\t\t\tdLinks: []model.DependencyLink{},\n\t\t},\n\t\t{\n\t\t\tdLinks: nil,\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tgot := FromDomainDependencies(test.dLinks)\n\t\t\ta := ToDomainDependencies(got)\n\t\t\tassert.Equal(t, test.dLinks, a)\n\t\t})\n\t}\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/dbmodel/model.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage dbmodel\n\nimport \"time\"\n\n// TimeDependencies encapsulates dependencies created at a given time\ntype TimeDependencies struct {\n\tTimestamp    time.Time        `json:\"timestamp\"`\n\tDependencies []DependencyLink `json:\"dependencies\"`\n}\n\n// DependencyLink shows dependencies between services\ntype DependencyLink struct {\n\tParent    string `json:\"parent\"`\n\tChild     string `json:\"child\"`\n\tCallCount uint64 `json:\"callCount\"`\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/mocks/mocks.go",
    "content": "// Copyright (c) The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Run 'make generate-mocks' to regenerate.\n\n// Code generated by mockery; DO NOT EDIT.\n// github.com/vektra/mockery\n// template: testify\n\npackage mocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// NewCoreDependencyStore creates a new instance of CoreDependencyStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.\n// The first argument is typically a *testing.T value.\nfunc NewCoreDependencyStore(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *CoreDependencyStore {\n\tmock := &CoreDependencyStore{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n\n// CoreDependencyStore is an autogenerated mock type for the CoreDependencyStore type\ntype CoreDependencyStore struct {\n\tmock.Mock\n}\n\ntype CoreDependencyStore_Expecter struct {\n\tmock *mock.Mock\n}\n\nfunc (_m *CoreDependencyStore) EXPECT() *CoreDependencyStore_Expecter {\n\treturn &CoreDependencyStore_Expecter{mock: &_m.Mock}\n}\n\n// CreateTemplates provides a mock function for the type CoreDependencyStore\nfunc (_mock *CoreDependencyStore) CreateTemplates(dependenciesTemplate string) error {\n\tret := _mock.Called(dependenciesTemplate)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for CreateTemplates\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(string) error); ok {\n\t\tr0 = returnFunc(dependenciesTemplate)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// CoreDependencyStore_CreateTemplates_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTemplates'\ntype CoreDependencyStore_CreateTemplates_Call struct {\n\t*mock.Call\n}\n\n// CreateTemplates is a helper method to define mock.On call\n//   - dependenciesTemplate string\nfunc (_e *CoreDependencyStore_Expecter) CreateTemplates(dependenciesTemplate interface{}) *CoreDependencyStore_CreateTemplates_Call {\n\treturn &CoreDependencyStore_CreateTemplates_Call{Call: _e.mock.On(\"CreateTemplates\", dependenciesTemplate)}\n}\n\nfunc (_c *CoreDependencyStore_CreateTemplates_Call) Run(run func(dependenciesTemplate string)) *CoreDependencyStore_CreateTemplates_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 string\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(string)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreDependencyStore_CreateTemplates_Call) Return(err error) *CoreDependencyStore_CreateTemplates_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *CoreDependencyStore_CreateTemplates_Call) RunAndReturn(run func(dependenciesTemplate string) error) *CoreDependencyStore_CreateTemplates_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// GetDependencies provides a mock function for the type CoreDependencyStore\nfunc (_mock *CoreDependencyStore) GetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]dbmodel.DependencyLink, error) {\n\tret := _mock.Called(ctx, endTs, lookback)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetDependencies\")\n\t}\n\n\tvar r0 []dbmodel.DependencyLink\n\tvar r1 error\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, time.Time, time.Duration) ([]dbmodel.DependencyLink, error)); ok {\n\t\treturn returnFunc(ctx, endTs, lookback)\n\t}\n\tif returnFunc, ok := ret.Get(0).(func(context.Context, time.Time, time.Duration) []dbmodel.DependencyLink); ok {\n\t\tr0 = returnFunc(ctx, endTs, lookback)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]dbmodel.DependencyLink)\n\t\t}\n\t}\n\tif returnFunc, ok := ret.Get(1).(func(context.Context, time.Time, time.Duration) error); ok {\n\t\tr1 = returnFunc(ctx, endTs, lookback)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\treturn r0, r1\n}\n\n// CoreDependencyStore_GetDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDependencies'\ntype CoreDependencyStore_GetDependencies_Call struct {\n\t*mock.Call\n}\n\n// GetDependencies is a helper method to define mock.On call\n//   - ctx context.Context\n//   - endTs time.Time\n//   - lookback time.Duration\nfunc (_e *CoreDependencyStore_Expecter) GetDependencies(ctx interface{}, endTs interface{}, lookback interface{}) *CoreDependencyStore_GetDependencies_Call {\n\treturn &CoreDependencyStore_GetDependencies_Call{Call: _e.mock.On(\"GetDependencies\", ctx, endTs, lookback)}\n}\n\nfunc (_c *CoreDependencyStore_GetDependencies_Call) Run(run func(ctx context.Context, endTs time.Time, lookback time.Duration)) *CoreDependencyStore_GetDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 context.Context\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(context.Context)\n\t\t}\n\t\tvar arg1 time.Time\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].(time.Time)\n\t\t}\n\t\tvar arg2 time.Duration\n\t\tif args[2] != nil {\n\t\t\targ2 = args[2].(time.Duration)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t\targ2,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreDependencyStore_GetDependencies_Call) Return(dependencyLinks []dbmodel.DependencyLink, err error) *CoreDependencyStore_GetDependencies_Call {\n\t_c.Call.Return(dependencyLinks, err)\n\treturn _c\n}\n\nfunc (_c *CoreDependencyStore_GetDependencies_Call) RunAndReturn(run func(ctx context.Context, endTs time.Time, lookback time.Duration) ([]dbmodel.DependencyLink, error)) *CoreDependencyStore_GetDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n\n// WriteDependencies provides a mock function for the type CoreDependencyStore\nfunc (_mock *CoreDependencyStore) WriteDependencies(ts time.Time, dependencies []dbmodel.DependencyLink) error {\n\tret := _mock.Called(ts, dependencies)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for WriteDependencies\")\n\t}\n\n\tvar r0 error\n\tif returnFunc, ok := ret.Get(0).(func(time.Time, []dbmodel.DependencyLink) error); ok {\n\t\tr0 = returnFunc(ts, dependencies)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\treturn r0\n}\n\n// CoreDependencyStore_WriteDependencies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteDependencies'\ntype CoreDependencyStore_WriteDependencies_Call struct {\n\t*mock.Call\n}\n\n// WriteDependencies is a helper method to define mock.On call\n//   - ts time.Time\n//   - dependencies []dbmodel.DependencyLink\nfunc (_e *CoreDependencyStore_Expecter) WriteDependencies(ts interface{}, dependencies interface{}) *CoreDependencyStore_WriteDependencies_Call {\n\treturn &CoreDependencyStore_WriteDependencies_Call{Call: _e.mock.On(\"WriteDependencies\", ts, dependencies)}\n}\n\nfunc (_c *CoreDependencyStore_WriteDependencies_Call) Run(run func(ts time.Time, dependencies []dbmodel.DependencyLink)) *CoreDependencyStore_WriteDependencies_Call {\n\t_c.Call.Run(func(args mock.Arguments) {\n\t\tvar arg0 time.Time\n\t\tif args[0] != nil {\n\t\t\targ0 = args[0].(time.Time)\n\t\t}\n\t\tvar arg1 []dbmodel.DependencyLink\n\t\tif args[1] != nil {\n\t\t\targ1 = args[1].([]dbmodel.DependencyLink)\n\t\t}\n\t\trun(\n\t\t\targ0,\n\t\t\targ1,\n\t\t)\n\t})\n\treturn _c\n}\n\nfunc (_c *CoreDependencyStore_WriteDependencies_Call) Return(err error) *CoreDependencyStore_WriteDependencies_Call {\n\t_c.Call.Return(err)\n\treturn _c\n}\n\nfunc (_c *CoreDependencyStore_WriteDependencies_Call) RunAndReturn(run func(ts time.Time, dependencies []dbmodel.DependencyLink) error) *CoreDependencyStore_WriteDependencies_Call {\n\t_c.Call.Return(run)\n\treturn _c\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/storage.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"go.uber.org/zap\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\tesquery \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/query\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n)\n\nconst (\n\tdependencyType          = \"dependencies\"\n\tdependencyIndexBaseName = \"jaeger-dependencies-\"\n)\n\n// CoreDependencyStore is a DB Level abstraction which directly read/write dependencies into ElasticSearch\ntype CoreDependencyStore interface {\n\t// WriteDependencies write dependencies to Elasticsearch\n\tWriteDependencies(ts time.Time, dependencies []dbmodel.DependencyLink) error\n\t// CreateTemplates creates index templates.\n\tCreateTemplates(dependenciesTemplate string) error\n\t// GetDependencies returns all interservice dependencies\n\tGetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]dbmodel.DependencyLink, error)\n}\n\n// DependencyStore handles all queries and insertions to ElasticSearch dependencies\ntype DependencyStore struct {\n\tclient                func() es.Client\n\tlogger                *zap.Logger\n\tdependencyIndexPrefix string\n\tindexDateLayout       string\n\tmaxDocCount           int\n\tuseReadWriteAliases   bool\n}\n\n// DependencyStoreParams holds constructor parameters for NewDependencyStore\ntype Params struct {\n\tClient              func() es.Client\n\tLogger              *zap.Logger\n\tIndexPrefix         config.IndexPrefix\n\tIndexDateLayout     string\n\tMaxDocCount         int\n\tUseReadWriteAliases bool\n}\n\n// NewDependencyStore returns a DependencyStore\nfunc NewDependencyStore(p Params) *DependencyStore {\n\treturn &DependencyStore{\n\t\tclient:                p.Client,\n\t\tlogger:                p.Logger,\n\t\tdependencyIndexPrefix: p.IndexPrefix.Apply(dependencyIndexBaseName),\n\t\tindexDateLayout:       p.IndexDateLayout,\n\t\tmaxDocCount:           p.MaxDocCount,\n\t\tuseReadWriteAliases:   p.UseReadWriteAliases,\n\t}\n}\n\n// WriteDependencies write dependencies to Elasticsearch\nfunc (s *DependencyStore) WriteDependencies(ts time.Time, dependencies []dbmodel.DependencyLink) error {\n\twriteIndexName := s.getWriteIndex(ts)\n\ts.writeDependenciesToIndex(writeIndexName, ts, dependencies)\n\treturn nil\n}\n\n// CreateTemplates creates index templates.\nfunc (s *DependencyStore) CreateTemplates(dependenciesTemplate string) error {\n\t_, err := s.client().CreateTemplate(\"jaeger-dependencies\").Body(dependenciesTemplate).Do(context.Background())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *DependencyStore) writeDependenciesToIndex(indexName string, ts time.Time, dependencies []dbmodel.DependencyLink) {\n\ts.client().Index().Index(indexName).Type(dependencyType).\n\t\tBodyJson(&dbmodel.TimeDependencies{\n\t\t\tTimestamp:    ts,\n\t\t\tDependencies: dependencies,\n\t\t}).Add()\n}\n\n// GetDependencies returns all interservice dependencies\nfunc (s *DependencyStore) GetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]dbmodel.DependencyLink, error) {\n\tindices := s.getReadIndices(endTs, lookback)\n\tsearchResult, err := s.client().Search(indices...).\n\t\tSize(s.maxDocCount).\n\t\tQuery(buildTSQuery(endTs, lookback)).\n\t\tIgnoreUnavailable(true).\n\t\tDo(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to search for dependencies: %w\", err)\n\t}\n\n\tvar retDependencies []dbmodel.DependencyLink\n\thits := searchResult.Hits.Hits\n\tfor _, hit := range hits {\n\t\tsource := hit.Source\n\t\tvar tToD dbmodel.TimeDependencies\n\t\tif err := json.Unmarshal(source, &tToD); err != nil {\n\t\t\treturn nil, errors.New(\"unmarshalling ElasticSearch documents failed\")\n\t\t}\n\t\tretDependencies = append(retDependencies, tToD.Dependencies...)\n\t}\n\treturn retDependencies, nil\n}\n\nfunc buildTSQuery(endTs time.Time, lookback time.Duration) elastic.Query {\n\treturn esquery.NewRangeQuery(\"timestamp\").Gte(endTs.Add(-lookback)).Lte(endTs)\n}\n\nfunc (s *DependencyStore) getReadIndices(ts time.Time, lookback time.Duration) []string {\n\tif s.useReadWriteAliases {\n\t\treturn []string{s.dependencyIndexPrefix + \"read\"}\n\t}\n\tvar indices []string\n\tfirstIndex := indexWithDate(s.dependencyIndexPrefix, s.indexDateLayout, ts.Add(-lookback))\n\tcurrentIndex := indexWithDate(s.dependencyIndexPrefix, s.indexDateLayout, ts)\n\tfor currentIndex != firstIndex {\n\t\tindices = append(indices, currentIndex)\n\t\tts = ts.Add(-24 * time.Hour)\n\t\tcurrentIndex = indexWithDate(s.dependencyIndexPrefix, s.indexDateLayout, ts)\n\t}\n\treturn append(indices, firstIndex)\n}\n\nfunc indexWithDate(indexNamePrefix, indexDateLayout string, date time.Time) string {\n\treturn indexNamePrefix + date.UTC().Format(indexDateLayout)\n}\n\nfunc (s *DependencyStore) getWriteIndex(ts time.Time) string {\n\tif s.useReadWriteAliases {\n\t\treturn s.dependencyIndexPrefix + \"write\"\n\t}\n\treturn indexWithDate(s.dependencyIndexPrefix, s.indexDateLayout, ts)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/storage_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/olivere/elastic/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\tes \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nconst defaultMaxDocCount = 10_000\n\ntype depStorageTest struct {\n\tclient    *mocks.Client\n\tlogger    *zap.Logger\n\tlogBuffer *testutils.Buffer\n\tstorage   *DependencyStore\n}\n\nfunc withDepStorage(indexPrefix config.IndexPrefix, indexDateLayout string, maxDocCount int, fn func(r *depStorageTest)) {\n\tclient := &mocks.Client{}\n\tlogger, logBuffer := testutils.NewLogger()\n\tr := &depStorageTest{\n\t\tclient:    client,\n\t\tlogger:    logger,\n\t\tlogBuffer: logBuffer,\n\t\tstorage: NewDependencyStore(Params{\n\t\t\tClient:          func() es.Client { return client },\n\t\t\tLogger:          logger,\n\t\t\tIndexPrefix:     indexPrefix,\n\t\t\tIndexDateLayout: indexDateLayout,\n\t\t\tMaxDocCount:     maxDocCount,\n\t\t}),\n\t}\n\tfn(r)\n}\n\nvar _ CoreDependencyStore = &DependencyStore{} // check API conformance\n\nfunc TestNewSpanReaderIndexPrefix(t *testing.T) {\n\ttestCases := []struct {\n\t\tprefix   config.IndexPrefix\n\t\texpected string\n\t}{\n\t\t{prefix: \"\", expected: \"\"},\n\t\t{prefix: \"foo\", expected: \"foo-\"},\n\t\t{prefix: \":\", expected: \":-\"},\n\t}\n\tfor _, testCase := range testCases {\n\t\tclient := &mocks.Client{}\n\t\tr := NewDependencyStore(Params{\n\t\t\tClient:          func() es.Client { return client },\n\t\t\tLogger:          zap.NewNop(),\n\t\t\tIndexPrefix:     testCase.prefix,\n\t\t\tIndexDateLayout: \"2006-01-02\",\n\t\t\tMaxDocCount:     defaultMaxDocCount,\n\t\t})\n\n\t\tassert.Equal(t, testCase.expected+dependencyIndexBaseName, r.dependencyIndexPrefix)\n\t}\n}\n\nfunc TestWriteDependencies(t *testing.T) {\n\ttestCases := []struct {\n\t\twriteError    error\n\t\texpectedError string\n\t\tesVersion     uint\n\t}{\n\t\t{\n\t\t\texpectedError: \"\",\n\t\t\tesVersion:     7,\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\twithDepStorage(\"\", \"2006-01-02\", defaultMaxDocCount, func(r *depStorageTest) {\n\t\t\tfixedTime := time.Date(1995, time.April, 21, 4, 21, 19, 95, time.UTC)\n\t\t\tindexName := indexWithDate(\"\", \"2006-01-02\", fixedTime)\n\t\t\twriteService := &mocks.IndexService{}\n\n\t\t\tr.client.On(\"Index\").Return(writeService)\n\t\t\tr.client.On(\"GetVersion\").Return(testCase.esVersion)\n\n\t\t\twriteService.On(\"Index\", stringMatcher(indexName)).Return(writeService)\n\t\t\twriteService.On(\"Type\", stringMatcher(dependencyType)).Return(writeService)\n\t\t\twriteService.On(\"BodyJson\", mock.Anything).Return(writeService)\n\t\t\twriteService.On(\"Add\", mock.Anything).Return(nil, testCase.writeError)\n\t\t\terr := r.storage.WriteDependencies(fixedTime, []dbmodel.DependencyLink{})\n\t\t\tif testCase.expectedError != \"\" {\n\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDependencies(t *testing.T) {\n\tgoodDependencies := `{\n\t\t\t\"ts\": 798434479000000,\n\t\t\t\"dependencies\": [\n\t\t\t\t{ \"parent\": \"hello\",\n\t\t\t\t  \"child\": \"world\",\n\t\t\t\t  \"callCount\": 12\n\t\t\t\t}\n\t\t\t]\n\t\t}`\n\tbadDependencies := `badJson{hello}world`\n\n\ttestCases := []struct {\n\t\tsearchResult   *elastic.SearchResult\n\t\tsearchError    error\n\t\texpectedError  string\n\t\texpectedOutput []dbmodel.DependencyLink\n\t\tindexPrefix    config.IndexPrefix\n\t\tmaxDocCount    int\n\t\tindices        []any\n\t}{\n\t\t{\n\t\t\tsearchResult: createSearchResult(goodDependencies),\n\t\t\texpectedOutput: []dbmodel.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"hello\",\n\t\t\t\t\tChild:     \"world\",\n\t\t\t\t\tCallCount: 12,\n\t\t\t\t},\n\t\t\t},\n\t\t\tindices:     []any{\"jaeger-dependencies-1995-04-21\", \"jaeger-dependencies-1995-04-20\"},\n\t\t\tmaxDocCount: 1000, // can be anything, assertion will check this value is used in search query.\n\t\t},\n\t\t{\n\t\t\tsearchResult:  createSearchResult(badDependencies),\n\t\t\texpectedError: \"unmarshalling ElasticSearch documents failed\",\n\t\t\tindices:       []any{\"jaeger-dependencies-1995-04-21\", \"jaeger-dependencies-1995-04-20\"},\n\t\t},\n\t\t{\n\t\t\tsearchError:   errors.New(\"search failure\"),\n\t\t\texpectedError: \"failed to search for dependencies: search failure\",\n\t\t\tindices:       []any{\"jaeger-dependencies-1995-04-21\", \"jaeger-dependencies-1995-04-20\"},\n\t\t},\n\t\t{\n\t\t\tsearchError:   errors.New(\"search failure\"),\n\t\t\texpectedError: \"failed to search for dependencies: search failure\",\n\t\t\tindexPrefix:   \"foo\",\n\t\t\tindices:       []any{\"foo-jaeger-dependencies-1995-04-21\", \"foo-jaeger-dependencies-1995-04-20\"},\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\twithDepStorage(testCase.indexPrefix, \"2006-01-02\", testCase.maxDocCount, func(r *depStorageTest) {\n\t\t\tfixedTime := time.Date(1995, time.April, 21, 4, 21, 19, 95, time.UTC)\n\n\t\t\tsearchService := &mocks.SearchService{}\n\t\t\tr.client.On(\"Search\", mock.AnythingOfType(\"[]string\")).Return(searchService)\n\n\t\t\tsearchService.On(\"Size\", mock.MatchedBy(func(size int) bool {\n\t\t\t\treturn size == testCase.maxDocCount\n\t\t\t})).Return(searchService)\n\t\t\tsearchService.On(\"Query\", mock.Anything).Return(searchService)\n\t\t\tsearchService.On(\"IgnoreUnavailable\", mock.AnythingOfType(\"bool\")).Return(searchService)\n\t\t\tsearchService.On(\"Do\", mock.Anything).Return(testCase.searchResult, testCase.searchError)\n\n\t\t\tactual, err := r.storage.GetDependencies(context.Background(), fixedTime, 24*time.Hour)\n\t\t\tif testCase.expectedError != \"\" {\n\t\t\t\trequire.EqualError(t, err, testCase.expectedError)\n\t\t\t\tassert.Nil(t, actual)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, testCase.expectedOutput, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createSearchResult(dependencyLink string) *elastic.SearchResult {\n\tdependencyLinkRaw := []byte(dependencyLink)\n\thits := make([]*elastic.SearchHit, 1)\n\thits[0] = &elastic.SearchHit{\n\t\tSource: dependencyLinkRaw,\n\t}\n\tsearchResult := &elastic.SearchResult{Hits: &elastic.SearchHits{Hits: hits}}\n\treturn searchResult\n}\n\nfunc TestGetReadIndices(t *testing.T) {\n\tfixedTime := time.Date(1995, time.April, 21, 4, 12, 19, 95, time.UTC)\n\ttestCases := []struct {\n\t\tindices  []string\n\t\tlookback time.Duration\n\t\tparams   Params\n\t}{\n\t\t{\n\t\t\tparams:   Params{IndexPrefix: \"\", IndexDateLayout: \"2006-01-02\", UseReadWriteAliases: true},\n\t\t\tlookback: 23 * time.Hour,\n\t\t\tindices: []string{\n\t\t\t\tdependencyIndexBaseName + \"read\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tparams:   Params{IndexPrefix: \"\", IndexDateLayout: \"2006-01-02\"},\n\t\t\tlookback: 23 * time.Hour,\n\t\t\tindices: []string{\n\t\t\t\tdependencyIndexBaseName + fixedTime.Format(\"2006-01-02\"),\n\t\t\t\tdependencyIndexBaseName + fixedTime.Add(-23*time.Hour).Format(\"2006-01-02\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tparams:   Params{IndexPrefix: \"\", IndexDateLayout: \"2006-01-02\"},\n\t\t\tlookback: 13 * time.Hour,\n\t\t\tindices: []string{\n\t\t\t\tdependencyIndexBaseName + fixedTime.UTC().Format(\"2006-01-02\"),\n\t\t\t\tdependencyIndexBaseName + fixedTime.Add(-13*time.Hour).Format(\"2006-01-02\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tparams:   Params{IndexPrefix: \"foo:\", IndexDateLayout: \"2006-01-02\"},\n\t\t\tlookback: 1 * time.Hour,\n\t\t\tindices: []string{\n\t\t\t\t\"foo:\" + config.IndexPrefixSeparator + dependencyIndexBaseName + fixedTime.Format(\"2006-01-02\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tparams:   Params{IndexPrefix: \"foo-\", IndexDateLayout: \"2006-01-02\"},\n\t\t\tlookback: 0,\n\t\t\tindices: []string{\n\t\t\t\t\"foo\" + config.IndexPrefixSeparator + dependencyIndexBaseName + fixedTime.Format(\"2006-01-02\"),\n\t\t\t},\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\ts := NewDependencyStore(testCase.params)\n\t\tassert.Equal(t, testCase.indices, s.getReadIndices(fixedTime, testCase.lookback))\n\t}\n}\n\nfunc TestGetWriteIndex(t *testing.T) {\n\tfixedTime := time.Date(1995, time.April, 21, 4, 12, 19, 95, time.UTC)\n\ttestCases := []struct {\n\t\twriteIndex string\n\t\tlookback   time.Duration\n\t\tparams     Params\n\t}{\n\t\t{\n\t\t\tparams:     Params{IndexPrefix: \"\", IndexDateLayout: \"2006-01-02\", UseReadWriteAliases: true},\n\t\t\twriteIndex: dependencyIndexBaseName + \"write\",\n\t\t},\n\t\t{\n\t\t\tparams:     Params{IndexPrefix: \"\", IndexDateLayout: \"2006-01-02\", UseReadWriteAliases: false},\n\t\t\twriteIndex: dependencyIndexBaseName + fixedTime.Format(\"2006-01-02\"),\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\ts := NewDependencyStore(testCase.params)\n\t\tassert.Equal(t, testCase.writeIndex, s.getWriteIndex(fixedTime))\n\t}\n}\n\n// stringMatcher can match a string argument when it contains a specific substring q\nfunc stringMatcher(q string) any {\n\tmatchFunc := func(s string) bool {\n\t\treturn strings.Contains(s, q)\n\t}\n\treturn mock.MatchedBy(matchFunc)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/storagev2.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n)\n\ntype DependencyStoreV2 struct {\n\tstore CoreDependencyStore\n}\n\n// NewDependencyStoreV2 returns a DependencyStoreV2\nfunc NewDependencyStoreV2(p Params) *DependencyStoreV2 {\n\treturn &DependencyStoreV2{\n\t\tstore: NewDependencyStore(p),\n\t}\n}\n\nfunc (s *DependencyStoreV2) GetDependencies(ctx context.Context, query depstore.QueryParameters) ([]model.DependencyLink, error) {\n\tdbDependencies, err := s.store.GetDependencies(ctx, query.EndTime, query.EndTime.Sub(query.StartTime))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdependencies := dbmodel.ToDomainDependencies(dbDependencies)\n\treturn dependencies, nil\n}\n\nfunc (s *DependencyStoreV2) WriteDependencies(ts time.Time, dependencies []model.DependencyLink) error {\n\tdbDependencies := dbmodel.FromDomainDependencies(dependencies)\n\treturn s.store.WriteDependencies(ts, dbDependencies)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/depstore/storagev2_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage depstore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore/mocks\"\n)\n\nfunc TestV2GetDependencies(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tmockOutput     []dbmodel.DependencyLink\n\t\tmockErr        error\n\t\texpectedOutput []model.DependencyLink\n\t\texpectedErr    string\n\t}{\n\t\t{\n\t\t\tname:        \"error from core reader\",\n\t\t\tmockErr:     errors.New(\"error from core reader\"),\n\t\t\texpectedErr: \"error from core reader\",\n\t\t},\n\t\t{\n\t\t\tname: \"success output\",\n\t\t\tmockOutput: []dbmodel.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"testing-parent\",\n\t\t\t\t\tChild:     \"testing-child\",\n\t\t\t\t\tCallCount: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedOutput: []model.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"testing-parent\",\n\t\t\t\t\tChild:     \"testing-child\",\n\t\t\t\t\tCallCount: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcoreReader := &mocks.CoreDependencyStore{}\n\t\t\tstore := DependencyStoreV2{\n\t\t\t\tstore: coreReader,\n\t\t\t}\n\t\t\tquery := depstore.QueryParameters{\n\t\t\t\tStartTime: time.Now(),\n\t\t\t\tEndTime:   time.Now(),\n\t\t\t}\n\t\t\tcoreReader.On(\"GetDependencies\", mock.Anything, query.EndTime, query.EndTime.Sub(query.StartTime)).Return(tt.mockOutput, tt.mockErr)\n\t\t\tactual, err := store.GetDependencies(context.Background(), query)\n\t\t\tif tt.expectedErr != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tt.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.expectedOutput, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestV2WriteDependencies(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\treturningErr error\n\t\texpectedErr  string\n\t}{\n\t\t{\n\t\t\tname:         \"error from core writer\",\n\t\t\treturningErr: errors.New(\"error from core writer\"),\n\t\t\texpectedErr:  \"error from core writer\",\n\t\t},\n\t\t{\n\t\t\tname: \"success\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcoreReader := &mocks.CoreDependencyStore{}\n\t\t\tstore := DependencyStoreV2{\n\t\t\t\tstore: coreReader,\n\t\t\t}\n\t\t\tcoreReader.On(\"WriteDependencies\", mock.Anything, mock.Anything).Return(tt.returningErr)\n\t\t\terr := store.WriteDependencies(time.Now(), []model.DependencyLink{})\n\t\t\tif tt.expectedErr != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tt.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewDependencyStoreV2(t *testing.T) {\n\tstore := NewDependencyStoreV2(Params{Logger: zap.NewNop()})\n\tassert.IsType(t, &DependencyStore{}, store.store)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\n\t\"go.opentelemetry.io/collector/extension/extensionauth\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/tracestoremetrics\"\n\tv2depstore \"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/depstore\"\n\tv2tracestore \"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nconst tagError = \"error\"\n\nvar (\n\t_ io.Closer          = (*Factory)(nil)\n\t_ tracestore.Factory = (*Factory)(nil)\n\t_ depstore.Factory   = (*Factory)(nil)\n)\n\ntype Factory struct {\n\tcoreFactory    *elasticsearch.FactoryBase\n\tconfig         escfg.Configuration\n\tmetricsFactory metrics.Factory\n}\n\nfunc NewFactory(ctx context.Context, cfg escfg.Configuration, telset telemetry.Settings, httpAuth extensionauth.HTTPClient) (*Factory, error) {\n\t// Ensure required fields are always included in tagsAsFields\n\tcfg = ensureRequiredFields(cfg)\n\n\tcoreFactory, err := elasticsearch.NewFactoryBase(ctx, cfg, telset.Metrics, telset.Logger, httpAuth)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf := &Factory{\n\t\tcoreFactory:    coreFactory,\n\t\tconfig:         cfg,\n\t\tmetricsFactory: telset.Metrics,\n\t}\n\treturn f, nil\n}\n\nfunc (f *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\tparams := f.coreFactory.GetSpanReaderParams()\n\treturn tracestoremetrics.NewReaderDecorator(v2tracestore.NewTraceReader(params), f.metricsFactory), nil\n}\n\nfunc (f *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\tparams := f.coreFactory.GetSpanWriterParams()\n\twr := v2tracestore.NewTraceWriter(params)\n\treturn wr, nil\n}\n\nfunc (f *Factory) CreateDependencyReader() (depstore.Reader, error) {\n\tparams := f.coreFactory.GetDependencyStoreParams()\n\treturn v2depstore.NewDependencyStoreV2(params), nil\n}\n\nfunc (f *Factory) CreateSamplingStore(maxBuckets int) (samplingstore.Store, error) {\n\treturn f.coreFactory.CreateSamplingStore(maxBuckets)\n}\n\nfunc (f *Factory) Close() error {\n\treturn f.coreFactory.Close()\n}\n\nfunc (f *Factory) Purge(ctx context.Context) error {\n\treturn f.coreFactory.Purge(ctx)\n}\n\n// ensureRequiredFields adds span.kind and span.status error to tags-as-fields configuration\n// regardless of user settings\nfunc ensureRequiredFields(cfg escfg.Configuration) escfg.Configuration {\n\tif cfg.Tags.AllAsFields {\n\t\treturn cfg\n\t}\n\n\t// Return new configuration with updated includes\n\tif cfg.Tags.Include != \"\" && !strings.HasSuffix(cfg.Tags.Include, \",\") {\n\t\tcfg.Tags.Include += \",\"\n\t}\n\tcfg.Tags.Include += model.SpanKindKey + \",\" + tagError\n\n\treturn cfg\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tescfg \"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nvar mockEsServerResponse = []byte(`\n{\n\t\"Version\": {\n\t\t\"Number\": \"6\"\n\t}\n}\n`)\n\n// func TestNewFactory(t *testing.T) {\n// \tcfg := escfg.Configuration{}\n// \tcoreFactory := getTestingFactoryBase(t, &cfg)\n// \tf := &Factory{coreFactory: coreFactory, config: cfg, metricsFactory: metrics.NullFactory}\n// \t_, err := f.CreateTraceReader()\n// \trequire.NoError(t, err)\n// \t_, err = f.CreateTraceWriter()\n// \trequire.NoError(t, err)\n// \t_, err = f.CreateDependencyReader()\n// \trequire.NoError(t, err)\n// \t_, err = f.CreateSamplingStore(1)\n// \trequire.NoError(t, err)\n// \terr = f.Close()\n// \trequire.NoError(t, err)\n// \terr = f.Purge(context.Background())\n// \trequire.NoError(t, err)\n// }\n\nfunc TestESStorageFactoryWithConfig(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tdefer server.Close()\n\tcfg := escfg.Configuration{\n\t\tServers:  []string{server.URL},\n\t\tLogLevel: \"error\",\n\t}\n\tfactory, err := NewFactory(context.Background(), cfg, telemetry.NoopSettings(), nil)\n\trequire.NoError(t, err)\n\tfactory.Close()\n}\n\nfunc TestESStorageFactoryErr(t *testing.T) {\n\tf, err := NewFactory(context.Background(), escfg.Configuration{}, telemetry.NoopSettings(), nil)\n\trequire.ErrorContains(t, err, \"failed to create Elasticsearch client: no servers specified\")\n\trequire.Nil(t, f)\n}\n\n// func getTestingFactoryBase(t *testing.T, cfg *escfg.Configuration) *elasticsearch.FactoryBase {\n// \tf := &elasticsearch.FactoryBase{}\n// \terr := elasticsearch.SetFactoryForTest(f, zaptest.NewLogger(t), metrics.NullFactory, cfg)\n// \trequire.NoError(t, err)\n// \treturn f\n// }\n\nfunc TestAlwaysIncludesRequiredTags(t *testing.T) {\n\t// Set up mock Elasticsearch server\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Write(mockEsServerResponse)\n\t}))\n\tdefer server.Close()\n\n\ttests := []struct {\n\t\tname       string\n\t\ttagsConfig escfg.TagsAsFields\n\t}{\n\t\t{\n\t\t\tname: \"Empty TagsAsFields with feature enabled\",\n\t\t\ttagsConfig: escfg.TagsAsFields{\n\t\t\t\tInclude: \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"With some tags with feature enabled\",\n\t\t\ttagsConfig: escfg.TagsAsFields{\n\t\t\t\tInclude: \"foo.bar,baz.qux\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"With one required tag already with feature enabled\",\n\t\t\ttagsConfig: escfg.TagsAsFields{\n\t\t\t\tInclude: \"span.kind\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcfg := escfg.Configuration{\n\t\t\t\tServers:  []string{server.URL},\n\t\t\t\tLogLevel: \"error\",\n\t\t\t\tTags:     tt.tagsConfig,\n\t\t\t}\n\t\t\tfactory, err := NewFactory(context.Background(), cfg, telemetry.NoopSettings(), nil)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer factory.Close()\n\n\t\t\t// Verify tag behavior based on test expectations\n\t\t\tincludeTags := factory.config.Tags.Include\n\n\t\t\trequire.Contains(t, includeTags, model.SpanKindKey)\n\t\t\trequire.Contains(t, includeTags, tagError)\n\t\t})\n\t}\n}\n\nfunc TestEnsureRequiredFields_AllAsFieldsTrue(t *testing.T) {\n\toriginalCfg := escfg.Configuration{\n\t\tTags: escfg.TagsAsFields{\n\t\t\tAllAsFields: true,\n\t\t\tInclude:     \"custom1,custom2,span.kind,error\",\n\t\t},\n\t}\n\n\t// Make an exact copy for comparison\n\texpectedCfg := originalCfg\n\tresult := ensureRequiredFields(originalCfg)\n\trequire.Equal(t, expectedCfg, result)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage elasticsearch\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/fixtures/.gitignore",
    "content": "actual_*"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/fixtures/es_01.json",
    "content": "{\n  \"traceID\": \"00000000000000010000000000000000\",\n  \"spanID\": \"0000000000000002\",\n  \"flags\": 1,\n  \"operationName\": \"test-general-conversion\",\n  \"references\": [\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"00000000000000010000000000000000\",\n      \"spanID\": \"0000000000000003\"\n    },\n    {\n      \"refType\": \"FOLLOWS_FROM\",\n      \"traceID\": \"00000000000000010000000000000000\",\n      \"spanID\": \"0000000000000004\"\n    },\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"00000000000000ff0000000000000000\",\n      \"spanID\": \"00000000000000ff\"\n    }\n  ],\n  \"startTime\": 1485467191639875,\n  \"startTimeMillis\": 1485467191639,\n  \"duration\": 5,\n  \"tags\": [\n    {\n      \"key\": \"otel.scope.name\",\n      \"type\": \"string\",\n      \"value\": \"testing-library\"\n    },\n    {\n      \"key\": \"otel.scope.version\",\n      \"type\": \"string\",\n      \"value\": \"1.1.1\"\n    },\n    {\n      \"key\": \"peer.service\",\n      \"type\": \"string\",\n      \"value\": \"service-y\"\n    },\n    {\n      \"key\": \"peer.ipv4\",\n      \"type\": \"int64\",\n      \"value\": 23456\n    },\n    {\n      \"key\": \"blob\",\n      \"type\": \"binary\",\n      \"value\": \"00003039\"\n    },\n    {\n      \"key\": \"temperature\",\n      \"type\": \"float64\",\n      \"value\": 72.5\n    },\n    {\n      \"key\": \"error\",\n      \"type\": \"bool\",\n      \"value\": true\n    }\n  ],\n  \"logs\": [\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"event\",\n          \"type\": \"string\",\n          \"value\": \"testing-event\"\n        },\n        {\n          \"key\": \"event-x\",\n          \"type\": \"string\",\n          \"value\": \"event-y\"\n        }\n      ]\n    },\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"x\",\n          \"type\": \"string\",\n          \"value\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"process\": {\n    \"serviceName\": \"service-x\",\n    \"tags\": [\n      {\n        \"key\": \"sdk.version\",\n        \"type\": \"string\",\n        \"value\": \"1.2.1\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/fixtures/es_01_string_tags.json",
    "content": "{\n  \"traceID\": \"00000000000000010000000000000000\",\n  \"spanID\": \"0000000000000002\",\n  \"flags\": 1,\n  \"operationName\": \"test-general-conversion\",\n  \"references\": [\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"00000000000000010000000000000000\",\n      \"spanID\": \"0000000000000003\"\n    },\n    {\n      \"refType\": \"FOLLOWS_FROM\",\n      \"traceID\": \"00000000000000010000000000000000\",\n      \"spanID\": \"0000000000000004\"\n    },\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"00000000000000ff0000000000000000\",\n      \"spanID\": \"00000000000000ff\"\n    }\n  ],\n  \"startTime\": 1485467191639875,\n  \"startTimeMillis\": 1485467191639,\n  \"duration\": 5,\n  \"tags\": [\n    {\n      \"key\": \"otel.scope.name\",\n      \"type\": \"string\",\n      \"value\": \"testing-library\"\n    },\n    {\n      \"key\": \"otel.scope.version\",\n      \"type\": \"string\",\n      \"value\": \"1.1.1\"\n    },\n    {\n      \"key\": \"peer.service\",\n      \"type\": \"string\",\n      \"value\": \"service-y\"\n    },\n    {\n      \"key\": \"peer.ipv4\",\n      \"type\": \"int64\",\n      \"value\": \"23456\"\n    },\n    {\n      \"key\": \"blob\",\n      \"type\": \"binary\",\n      \"value\": \"00003039\"\n    },\n    {\n      \"key\": \"temperature\",\n      \"type\": \"float64\",\n      \"value\": \"72.5\"\n    },\n    {\n      \"key\": \"error\",\n      \"type\": \"bool\",\n      \"value\": \"true\"\n    }\n  ],\n  \"logs\": [\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"event\",\n          \"type\": \"string\",\n          \"value\": \"testing-event\"\n        },\n        {\n          \"key\": \"event-x\",\n          \"type\": \"string\",\n          \"value\": \"event-y\"\n        }\n      ]\n    },\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"x\",\n          \"type\": \"string\",\n          \"value\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"process\": {\n    \"serviceName\": \"service-x\",\n    \"tags\": [\n      {\n        \"key\": \"sdk.version\",\n        \"type\": \"string\",\n        \"value\": \"1.2.1\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/fixtures/otel_traces_01.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"service-x\"\n            }\n          },\n          {\n            \"key\": \"sdk.version\",\n            \"value\": {\n              \"stringValue\": \"1.2.1\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {\n            \"name\": \"testing-library\",\n            \"version\": \"1.1.1\"\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000010000000000000000\",\n              \"spanId\": \"0000000000000002\",\n              \"parentSpanId\": \"0000000000000003\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/from_dbmodel.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/jaegerproto_to_traces.go\n\npackage tracestore\n\nimport (\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\tconventions \"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nvar errType = errors.New(\"invalid type\")\n\n// FromDBModel converts multiple ES DB Spans to internal traces\nfunc FromDBModel(spans []dbmodel.Span) (ptrace.Traces, error) {\n\ttraceData := ptrace.NewTraces()\n\tif len(spans) == 0 {\n\t\treturn traceData, nil\n\t}\n\n\tresourceSpans := traceData.ResourceSpans()\n\tresourceSpans.EnsureCapacity(len(spans))\n\terr := dbSpansToSpans(spans, resourceSpans)\n\n\treturn traceData, err\n}\n\nfunc dbProcessToResource(process dbmodel.Process, resource pcommon.Resource) {\n\tserviceName := process.ServiceName\n\ttags := process.Tags\n\tif serviceName == \"\" && tags == nil {\n\t\treturn\n\t}\n\n\tattrs := resource.Attributes()\n\tif serviceName != \"\" && serviceName != noServiceName {\n\t\tattrs.EnsureCapacity(len(tags) + 1)\n\t\tattrs.PutStr(string(conventions.ServiceNameKey), serviceName)\n\t} else {\n\t\tattrs.EnsureCapacity(len(tags))\n\t}\n\tdbTagsToAttributes(tags, attrs)\n}\n\nfunc dbSpansToSpans(dbSpans []dbmodel.Span, resourceSpans ptrace.ResourceSpansSlice) error {\n\tfor i := range dbSpans {\n\t\tspan := &dbSpans[i]\n\t\tresourceSpan := resourceSpans.AppendEmpty()\n\t\tdbProcessToResource(span.Process, resourceSpan.Resource())\n\n\t\tscopeSpans := resourceSpan.ScopeSpans()\n\t\tscopeSpan := scopeSpans.AppendEmpty()\n\t\tdbSpanToScope(span, scopeSpan)\n\n\t\t// TODO there should be no errors returned from translation from db model\n\t\terr := dbSpanToSpan(span, scopeSpan.Spans().AppendEmpty())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc dbSpanToSpan(dbSpan *dbmodel.Span, span ptrace.Span) error {\n\ttraceId, err := convertTraceIDFromDB(dbSpan.TraceID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tspanId, err := fromDbSpanId(dbSpan.SpanID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif dbSpan.ParentSpanID != \"\" {\n\t\tparentSpanId, err := fromDbSpanId(dbSpan.ParentSpanID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tspan.SetParentSpanID(parentSpanId)\n\t}\n\tspan.SetTraceID(traceId)\n\tspan.SetSpanID(spanId)\n\tspan.SetName(dbSpan.OperationName)\n\tspan.SetFlags(dbSpan.Flags)\n\n\tstartTime := model.EpochMicrosecondsAsTime(dbSpan.StartTime)\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(startTime))\n\n\tduration := model.MicrosecondsAsDuration(dbSpan.Duration)\n\tendTime := startTime.Add(duration)\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(endTime))\n\n\t// TODO rewrite this to use a single loop over tags\n\t// and map special tag names to OTEL Span fields\n\tattrs := span.Attributes()\n\tattrs.EnsureCapacity(len(dbSpan.Tags))\n\tdbTagsToAttributes(dbSpan.Tags, attrs)\n\tif spanKindAttr, ok := attrs.Get(model.SpanKindKey); ok {\n\t\tspan.SetKind(dbSpanKindToOTELSpanKind(spanKindAttr.Str()))\n\t\tattrs.Remove(model.SpanKindKey)\n\t}\n\tsetSpanStatus(attrs, span)\n\n\tspan.TraceState().FromRaw(getTraceStateFromAttrs(attrs))\n\n\t// drop the attributes slice if all of them were replaced during translation\n\tif attrs.Len() == 0 {\n\t\tattrs.Clear()\n\t}\n\tdbParentSpanId := getParentSpanId(dbSpan)\n\tif dbParentSpanId != \"\" {\n\t\tparentSpanId, err := fromDbSpanId(dbParentSpanId)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tspan.SetParentSpanID(parentSpanId)\n\t}\n\tdbSpanLogsToSpanEvents(dbSpan.Logs, span.Events())\n\treturn dbSpanRefsToSpanEvents(dbSpan.References, dbParentSpanId, span.Links())\n}\n\nfunc dbTagsToAttributes(tags []dbmodel.KeyValue, attributes pcommon.Map) {\n\tfor _, tag := range tags {\n\t\ttagValue, ok := tag.Value.(string)\n\t\tif !ok {\n\t\t\tswitch tag.Type {\n\t\t\tcase dbmodel.Float64Type, dbmodel.Int64Type:\n\t\t\t\tfromDBNumber(tag, attributes)\n\t\t\tcase dbmodel.BoolType:\n\t\t\t\tv, ok := tag.Value.(bool)\n\t\t\t\tif !ok {\n\t\t\t\t\trecordTagInvalidTypeError(tag, attributes)\n\t\t\t\t} else {\n\t\t\t\t\tattributes.PutBool(tag.Key, v)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// This means type is string/binary but value is of non string type, hence record the type error\n\t\t\t\trecordTagInvalidTypeError(tag, attributes)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tswitch tag.Type {\n\t\tcase dbmodel.StringType:\n\t\t\tattributes.PutStr(tag.Key, tagValue)\n\t\tcase dbmodel.BoolType:\n\t\t\tconvBoolVal, err := strconv.ParseBool(tagValue)\n\t\t\tif err != nil {\n\t\t\t\trecordTagConversionError(tag, err, attributes)\n\t\t\t} else {\n\t\t\t\tattributes.PutBool(tag.Key, convBoolVal)\n\t\t\t}\n\t\tcase dbmodel.Int64Type:\n\t\t\tintVal, err := strconv.ParseInt(tagValue, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\trecordTagConversionError(tag, err, attributes)\n\t\t\t} else {\n\t\t\t\tattributes.PutInt(tag.Key, intVal)\n\t\t\t}\n\t\tcase dbmodel.Float64Type:\n\t\t\tfloatVal, err := strconv.ParseFloat(tagValue, 64)\n\t\t\tif err != nil {\n\t\t\t\trecordTagConversionError(tag, err, attributes)\n\t\t\t} else {\n\t\t\t\tattributes.PutDouble(tag.Key, floatVal)\n\t\t\t}\n\t\tcase dbmodel.BinaryType:\n\t\t\tvalue, err := hex.DecodeString(tagValue)\n\t\t\tif err != nil {\n\t\t\t\trecordTagConversionError(tag, err, attributes)\n\t\t\t} else {\n\t\t\t\tattributes.PutEmptyBytes(tag.Key).FromRaw(value)\n\t\t\t}\n\t\tdefault:\n\t\t\tattributes.PutStr(tag.Key, fmt.Sprintf(\"<Unknown Jaeger TagType %q>\", tag.Type))\n\t\t}\n\t}\n}\n\nfunc fromDBNumber(kv dbmodel.KeyValue, dest pcommon.Map) {\n\tswitch kv.Type {\n\tcase dbmodel.Int64Type:\n\t\tswitch v := kv.Value.(type) {\n\t\tcase int64:\n\t\t\tdest.PutInt(kv.Key, v)\n\t\tcase float64:\n\t\t\t// This case is possible as unmarshalling the JSON converts every int value to float64\n\t\t\tdest.PutInt(kv.Key, int64(v))\n\t\tcase json.Number:\n\t\t\tn, err := v.Int64()\n\t\t\tif err == nil {\n\t\t\t\tdest.PutInt(kv.Key, n)\n\t\t\t}\n\t\tdefault:\n\t\t\trecordTagInvalidTypeError(kv, dest)\n\t\t}\n\tcase dbmodel.Float64Type:\n\t\tswitch v := kv.Value.(type) {\n\t\tcase float64:\n\t\t\tdest.PutDouble(kv.Key, v)\n\t\tcase json.Number:\n\t\t\tn, err := v.Float64()\n\t\t\tif err == nil {\n\t\t\t\tdest.PutDouble(kv.Key, n)\n\t\t\t}\n\t\tdefault:\n\t\t\trecordTagInvalidTypeError(kv, dest)\n\t\t}\n\tdefault:\n\t}\n}\n\nfunc recordTagInvalidTypeError(kv dbmodel.KeyValue, dest pcommon.Map) {\n\tdest.PutStr(kv.Key, fmt.Sprintf(\"invalid %s type in %+v\", string(kv.Type), kv.Value))\n}\n\nfunc recordTagConversionError(kv dbmodel.KeyValue, err error, dest pcommon.Map) {\n\tdest.PutStr(kv.Key, fmt.Sprintf(\"Can't convert the type %s for the key %s: %v\", string(kv.Type), kv.Key, err))\n}\n\nfunc setSpanStatus(attrs pcommon.Map, span ptrace.Span) {\n\tdest := span.Status()\n\tstatusCode := ptrace.StatusCodeUnset\n\tstatusMessage := \"\"\n\tstatusExists := false\n\n\tif errorVal, ok := attrs.Get(tagError); ok && errorVal.Type() == pcommon.ValueTypeBool {\n\t\tif errorVal.Bool() {\n\t\t\tstatusCode = ptrace.StatusCodeError\n\t\t\tattrs.Remove(tagError)\n\t\t\tstatusExists = true\n\t\t\tif desc, ok := extractStatusDescFromAttr(attrs); ok {\n\t\t\t\tstatusMessage = desc\n\t\t\t} else if descAttr, ok := attrs.Get(tagHTTPStatusMsg); ok {\n\t\t\t\tstatusMessage = descAttr.Str()\n\t\t\t}\n\t\t}\n\t}\n\n\tif codeAttr, ok := attrs.Get(conventions.OtelStatusCode); ok {\n\t\tif !statusExists {\n\t\t\t// The error tag is the ultimate truth for a Jaeger spans' error\n\t\t\t// status. Only parse the otel.status_code tag if the error tag is\n\t\t\t// not set to true.\n\t\t\tstatusExists = true\n\t\t\tswitch strings.ToUpper(codeAttr.Str()) {\n\t\t\tcase statusOk:\n\t\t\t\tstatusCode = ptrace.StatusCodeOk\n\t\t\tcase statusError:\n\t\t\t\tstatusCode = ptrace.StatusCodeError\n\t\t\tdefault:\n\t\t\t\tstatusCode = ptrace.StatusCodeUnset\n\t\t\t}\n\n\t\t\tif desc, ok := extractStatusDescFromAttr(attrs); ok {\n\t\t\t\tstatusMessage = desc\n\t\t\t}\n\t\t}\n\t\t// Regardless of error tag inputValue, remove the otel.status_code tag. The\n\t\t// otel.status_message tag will have already been removed if\n\t\t// statusExists is true.\n\t\tattrs.Remove(conventions.OtelStatusCode)\n\t} else if httpCodeAttr, ok := attrs.Get(string(conventions.HTTPResponseStatusCodeKey)); !statusExists && ok {\n\t\t// Fallback to introspecting if this span represents a failed HTTP\n\t\t// request or response, but again, only do so if the `error` tag was\n\t\t// not set to true and no explicit status was sent.\n\t\tif code, err := getStatusCodeFromHTTPStatusAttr(httpCodeAttr, span.Kind()); err == nil {\n\t\t\tif code != ptrace.StatusCodeUnset {\n\t\t\t\tstatusExists = true\n\t\t\t\tstatusCode = code\n\t\t\t}\n\n\t\t\tif msgAttr, ok := attrs.Get(tagHTTPStatusMsg); ok {\n\t\t\t\tstatusMessage = msgAttr.Str()\n\t\t\t}\n\t\t}\n\t}\n\n\tif statusExists {\n\t\tdest.SetCode(statusCode)\n\t\tdest.SetMessage(statusMessage)\n\t}\n}\n\n// extractStatusDescFromAttr returns the OTel status description from attrs\n// along with true if it is set. Otherwise, an empty string and false are\n// returned. The OTel status description attribute is deleted from attrs in\n// the process.\nfunc extractStatusDescFromAttr(attrs pcommon.Map) (string, bool) {\n\tif msgAttr, ok := attrs.Get(conventions.OtelStatusDescription); ok {\n\t\tmsg := msgAttr.Str()\n\t\tattrs.Remove(conventions.OtelStatusDescription)\n\t\treturn msg, true\n\t}\n\treturn \"\", false\n}\n\n// codeFromAttr returns the integer code inputValue from attrVal. An error is\n// returned if the code is not represented by an integer or string inputValue in\n// the attrVal or the inputValue is outside the bounds of an int representation.\nfunc codeFromAttr(attrVal pcommon.Value) (int64, error) {\n\tvar val int64\n\tswitch attrVal.Type() {\n\tcase pcommon.ValueTypeInt:\n\t\tval = attrVal.Int()\n\tcase pcommon.ValueTypeStr:\n\t\tvar err error\n\t\tval, err = strconv.ParseInt(attrVal.Str(), 10, 0)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"%w: %s\", errType, attrVal.Type().String())\n\t}\n\treturn val, nil\n}\n\nfunc getStatusCodeFromHTTPStatusAttr(attrVal pcommon.Value, kind ptrace.SpanKind) (ptrace.StatusCode, error) {\n\tstatusCode, err := codeFromAttr(attrVal)\n\tif err != nil {\n\t\treturn ptrace.StatusCodeUnset, err\n\t}\n\n\t// For HTTP status codes in the 4xx range span status MUST be left unset\n\t// in case of SpanKind.SERVER and MUST be set to Error in case of SpanKind.CLIENT.\n\t// For HTTP status codes in the 5xx range, as well as any other code the client\n\t// failed to interpret, span status MUST be set to Error.\n\tif statusCode >= 400 && statusCode < 500 {\n\t\tswitch kind {\n\t\tcase ptrace.SpanKindServer:\n\t\t\treturn ptrace.StatusCodeUnset, nil\n\t\tdefault:\n\t\t\treturn ptrace.StatusCodeError, nil\n\t\t}\n\t}\n\n\treturn statusCodeFromHTTP(statusCode), nil\n}\n\n// StatusCodeFromHTTP takes an HTTP status code and return the appropriate OpenTelemetry status code\n// See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status\nfunc statusCodeFromHTTP(httpStatusCode int64) ptrace.StatusCode {\n\tif httpStatusCode >= 100 && httpStatusCode < 399 {\n\t\treturn ptrace.StatusCodeUnset\n\t}\n\treturn ptrace.StatusCodeError\n}\n\nfunc dbSpanKindToOTELSpanKind(spanKind string) ptrace.SpanKind {\n\tswitch spanKind {\n\tcase \"client\":\n\t\treturn ptrace.SpanKindClient\n\tcase \"server\":\n\t\treturn ptrace.SpanKindServer\n\tcase \"producer\":\n\t\treturn ptrace.SpanKindProducer\n\tcase \"consumer\":\n\t\treturn ptrace.SpanKindConsumer\n\tcase \"internal\":\n\t\treturn ptrace.SpanKindInternal\n\tdefault:\n\t\treturn ptrace.SpanKindUnspecified\n\t}\n}\n\nfunc dbSpanLogsToSpanEvents(logs []dbmodel.Log, events ptrace.SpanEventSlice) {\n\tif len(logs) == 0 {\n\t\treturn\n\t}\n\n\tevents.EnsureCapacity(len(logs))\n\n\tfor i, log := range logs {\n\t\tvar event ptrace.SpanEvent\n\t\tif events.Len() > i {\n\t\t\tevent = events.At(i)\n\t\t} else {\n\t\t\tevent = events.AppendEmpty()\n\t\t}\n\n\t\tevent.SetTimestamp(pcommon.NewTimestampFromTime(model.EpochMicrosecondsAsTime(log.Timestamp)))\n\t\tif len(log.Fields) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tattrs := event.Attributes()\n\t\tattrs.EnsureCapacity(len(log.Fields))\n\t\tdbTagsToAttributes(log.Fields, attrs)\n\t\tif name, ok := attrs.Get(eventNameAttr); ok && name.Type() == pcommon.ValueTypeStr {\n\t\t\tevent.SetName(name.Str())\n\t\t\tattrs.Remove(eventNameAttr)\n\t\t}\n\t}\n}\n\n// dbSpanRefsToSpanEvents sets internal span links based on db references skipping excludeParentID\nfunc dbSpanRefsToSpanEvents(refs []dbmodel.Reference, excludeParentID dbmodel.SpanID, spanLinks ptrace.SpanLinkSlice) error {\n\tif len(refs) == 0 || len(refs) == 1 && refs[0].SpanID == excludeParentID && refs[0].RefType == dbmodel.ChildOf {\n\t\treturn nil\n\t}\n\n\tspanLinks.EnsureCapacity(len(refs))\n\tfor _, ref := range refs {\n\t\tif ref.SpanID == excludeParentID && ref.RefType == dbmodel.ChildOf {\n\t\t\tcontinue\n\t\t}\n\n\t\tlink := spanLinks.AppendEmpty()\n\t\trefTraceId, err := convertTraceIDFromDB(ref.TraceID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trefSpanId, err := fromDbSpanId(ref.SpanID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlink.SetTraceID(refTraceId)\n\t\tlink.SetSpanID(refSpanId)\n\t\tlink.Attributes().PutStr(conventions.AttributeOpentracingRefType, dbRefTypeToAttribute(ref.RefType))\n\t}\n\treturn nil\n}\n\nfunc getTraceStateFromAttrs(attrs pcommon.Map) string {\n\ttraceState := \"\"\n\t// TODO Bring this inline with solution for jaegertracing/jaeger-client-java #702 once available\n\tif attr, ok := attrs.Get(tagW3CTraceState); ok {\n\t\ttraceState = attr.Str()\n\t\tattrs.Remove(tagW3CTraceState)\n\t}\n\treturn traceState\n}\n\nfunc dbSpanToScope(span *dbmodel.Span, scopeSpan ptrace.ScopeSpans) {\n\tif libraryName, ok := getAndDeleteTag(span, conventions.AttributeOtelScopeName); ok {\n\t\tscopeSpan.Scope().SetName(libraryName)\n\t\tif libraryVersion, ok := getAndDeleteTag(span, conventions.AttributeOtelScopeVersion); ok {\n\t\t\tscopeSpan.Scope().SetVersion(libraryVersion)\n\t\t}\n\t}\n}\n\nfunc getAndDeleteTag(span *dbmodel.Span, key string) (string, bool) {\n\tfor i := range span.Tags {\n\t\tif span.Tags[i].Key == key {\n\t\t\tif val, ok := span.Tags[i].Value.(string); ok {\n\t\t\t\tspan.Tags = append(span.Tags[:i], span.Tags[i+1:]...)\n\t\t\t\treturn val, true\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc dbRefTypeToAttribute(ref dbmodel.ReferenceType) string {\n\tif ref == dbmodel.ChildOf {\n\t\treturn conventions.AttributeOpentracingRefTypeChildOf\n\t}\n\treturn conventions.AttributeOpentracingRefTypeFollowsFrom\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/from_dbmodel_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/jaegerproto_to_traces_test.go\n\npackage tracestore\n\nimport (\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\tconventions \"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nvar testSpanEventTime = time.Date(2020, 2, 11, 20, 26, 13, 123000, time.UTC)\n\nfunc TestCodeFromAttr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tattr pcommon.Value\n\t\tcode int64\n\t\terr  error\n\t}{\n\t\t{\n\t\t\tname: \"ok-string\",\n\t\t\tattr: pcommon.NewValueStr(\"0\"),\n\t\t\tcode: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"ok-int\",\n\t\t\tattr: pcommon.NewValueInt(1),\n\t\t\tcode: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong-type\",\n\t\t\tattr: pcommon.NewValueBool(true),\n\t\t\tcode: 0,\n\t\t\terr:  errType,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid-string\",\n\t\t\tattr: pcommon.NewValueStr(\"inf\"),\n\t\t\tcode: 0,\n\t\t\terr:  strconv.ErrSyntax,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcode, err := codeFromAttr(test.attr)\n\t\t\tif test.err != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.code, code)\n\t\t})\n\t}\n}\n\nfunc TestZeroBatchLength(t *testing.T) {\n\ttrace, err := FromDBModel([]dbmodel.Span{})\n\trequire.NoError(t, err)\n\tassert.Equal(t, 0, trace.ResourceSpans().Len())\n}\n\nfunc TestEmptySpansAndProcess(t *testing.T) {\n\ttrace, err := FromDBModel([]dbmodel.Span{})\n\trequire.NoError(t, err)\n\tassert.Equal(t, 0, trace.ResourceSpans().Len())\n}\n\nfunc TestGetStatusCodeFromHTTPStatusAttr(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tattr pcommon.Value\n\t\tkind ptrace.SpanKind\n\t\tcode ptrace.StatusCode\n\t\terr  string\n\t}{\n\t\t{\n\t\t\tname: \"string-unknown\",\n\t\t\tattr: pcommon.NewValueStr(\"10\"),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"string-ok\",\n\t\t\tattr: pcommon.NewValueStr(\"101\"),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeUnset,\n\t\t},\n\t\t{\n\t\t\tname: \"int-not-found\",\n\t\t\tattr: pcommon.NewValueInt(404),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"int-not-found-client-span\",\n\t\t\tattr: pcommon.NewValueInt(404),\n\t\t\tkind: ptrace.SpanKindServer,\n\t\t\tcode: ptrace.StatusCodeUnset,\n\t\t},\n\t\t{\n\t\t\tname: \"int-invalid-arg\",\n\t\t\tattr: pcommon.NewValueInt(408),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"int-internal\",\n\t\t\tattr: pcommon.NewValueInt(500),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong inputValue\",\n\t\t\tattr: pcommon.NewValueBool(true),\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\tcode: ptrace.StatusCodeUnset,\n\t\t\terr:  \"invalid type: Bool\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcode, err := getStatusCodeFromHTTPStatusAttr(test.attr, test.kind)\n\t\t\tif test.err != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.code, code)\n\t\t})\n\t}\n}\n\nfunc TestGetStatusCodeFromHTTPStatusAttr_DefaultSpanKind(t *testing.T) {\n\tvalue := pcommon.NewValueInt(404)\n\n\tstatusCode, err := getStatusCodeFromHTTPStatusAttr(value, ptrace.SpanKindInternal)\n\trequire.NoError(t, err)\n\tassert.Equal(t, ptrace.StatusCodeError, statusCode)\n\n\tstatusCode, err = getStatusCodeFromHTTPStatusAttr(value, ptrace.SpanKindProducer)\n\trequire.NoError(t, err)\n\tassert.Equal(t, ptrace.StatusCodeError, statusCode)\n\n\tstatusCode, err = getStatusCodeFromHTTPStatusAttr(value, ptrace.SpanKindConsumer)\n\trequire.NoError(t, err)\n\tassert.Equal(t, ptrace.StatusCodeError, statusCode)\n}\n\nfunc Test_SetSpanEventsFromDbSpanLogs(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\tspan := traces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.Events().AppendEmpty().SetName(\"event1\")\n\tspan.Events().AppendEmpty().SetName(\"event2\")\n\tspan.Events().AppendEmpty().Attributes().PutStr(eventNameAttr, \"testing\")\n\n\tlogs := []dbmodel.Log{\n\t\t{\n\t\t\tTimestamp: model.TimeAsEpochMicroseconds(testSpanEventTime),\n\t\t},\n\t\t{\n\t\t\tTimestamp: model.TimeAsEpochMicroseconds(testSpanEventTime),\n\t\t},\n\t}\n\n\tdbSpanLogsToSpanEvents(logs, span.Events())\n\tfor i := range logs {\n\t\tassert.Equal(t, testSpanEventTime, span.Events().At(i).Timestamp().AsTime())\n\t}\n\tassert.Equal(t, 1, span.Events().At(2).Attributes().Len())\n\tassert.Empty(t, span.Events().At(2).Name())\n}\n\nfunc TestSetAttributesFromDbTags(t *testing.T) {\n\twrongValue := \"wrong-inputValue\"\n\ttests := []struct {\n\t\tname            string\n\t\tkeyModel        dbmodel.KeyValue\n\t\texpectedValueFn func(pcommon.Map)\n\t}{\n\t\t{\n\t\t\tname: \"wrong bool input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"bool-val\",\n\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\tValue: wrongValue,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"bool-val\", \"Can't convert the type bool for the key bool-val: strconv.ParseBool: parsing \\\"wrong-inputValue\\\": invalid syntax\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right bool input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"bool-val\",\n\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\tValue: \"true\",\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutBool(\"bool-val\", true)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"non string bool value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"bool-val\",\n\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutBool(\"bool-val\", true)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong non string int input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"bool-val\",\n\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\tValue: 12,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"bool-val\", \"invalid bool type in 12\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong int input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: wrongValue,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"int-val\", \"Can't convert the type int64 for the key int-val: strconv.ParseInt: parsing \\\"wrong-inputValue\\\": invalid syntax\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right int input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: \"123\",\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutInt(\"int-val\", 123)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right non string int input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: int64(123),\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutInt(\"int-val\", 123)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right non string int float input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: float64(123),\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutInt(\"int-val\", 123)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right non string json number int input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: json.Number(\"123\"),\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutInt(\"int-val\", 123)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong non string int input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"int-val\",\n\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"int-val\", \"invalid int64 type in true\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong double input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"double-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: wrongValue,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"double-val\", \"Can't convert the type float64 for the key double-val: strconv.ParseFloat: parsing \\\"wrong-inputValue\\\": invalid syntax\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right double input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"double-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: \"1.23\",\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutDouble(\"double-val\", 1.23)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right non string double input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"double-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: 25.6,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutDouble(\"double-val\", 25.6)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right non string json number double input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"double-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: json.Number(\"123.56\"),\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutDouble(\"double-val\", 123.56)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong non string float input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"double-val\",\n\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"double-val\", \"invalid float64 type in true\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong binary input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"binary-val\",\n\t\t\t\tType:  dbmodel.BinaryType,\n\t\t\t\tValue: wrongValue,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"binary-val\", \"Can't convert the type binary for the key binary-val: encoding/hex: invalid byte: U+0077 'w'\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right binary input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"binary-val\",\n\t\t\t\tType:  dbmodel.BinaryType,\n\t\t\t\tValue: hex.EncodeToString([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7D, 0x98}),\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutEmptyBytes(\"binary-val\").FromRaw([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7D, 0x98})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"non-string input value with string type\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"bool-val\",\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: 123,\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"bool-val\", \"invalid string type in 123\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"right string input value\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"string-val\",\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: \"right-value\",\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"string-val\", \"right-value\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"unknown type\",\n\t\t\tkeyModel: dbmodel.KeyValue{\n\t\t\t\tKey:   \"unknown\",\n\t\t\t\tType:  dbmodel.ValueType(\"unknown\"),\n\t\t\t\tValue: \"any\",\n\t\t\t},\n\t\t\texpectedValueFn: func(p pcommon.Map) {\n\t\t\t\tp.PutStr(\"unknown\", \"<Unknown Jaeger TagType \\\"unknown\\\">\")\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\texpected := pcommon.NewMap()\n\t\t\ttest.expectedValueFn(expected)\n\t\t\tgot := pcommon.NewMap()\n\t\t\tdbTagsToAttributes([]dbmodel.KeyValue{test.keyModel}, got)\n\t\t\tassert.Equal(t, expected, got)\n\t\t})\n\t}\n}\n\nfunc TestFromDBModelErrors(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\terr     string\n\t\tdbSpans []dbmodel.Span\n\t}{\n\t\t{\n\t\t\tname:    \"wrong trace-id\",\n\t\t\tdbSpans: []dbmodel.Span{{TraceID: dbmodel.TraceID(\"trace-id\")}},\n\t\t\terr:     \"encoding/hex: invalid byte: U+0074 't'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"wrong span-id\",\n\t\t\tdbSpans: []dbmodel.Span{{SpanID: dbmodel.SpanID(\"span-id\")}},\n\t\t\terr:     \"encoding/hex: invalid byte: U+0073 's'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"wrong parent span-id\",\n\t\t\tdbSpans: []dbmodel.Span{{ParentSpanID: dbmodel.SpanID(\"parent-span-id\")}},\n\t\t\terr:     \"encoding/hex: invalid byte: U+0070 'p'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"wrong-ref-trace-id\",\n\t\t\tdbSpans: []dbmodel.Span{{References: []dbmodel.Reference{{TraceID: dbmodel.TraceID(\"ref-trace-id\")}}}},\n\t\t\terr:     \"encoding/hex: invalid byte: U+0072 'r'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"wrong-ref-span-id\",\n\t\t\tdbSpans: []dbmodel.Span{{References: []dbmodel.Reference{{SpanID: dbmodel.SpanID(\"ref-span-id\")}}}},\n\t\t\terr:     \"encoding/hex: invalid byte: U+0072 'r'\",\n\t\t},\n\t\t{\n\t\t\tname: \"wrong parent span-id with valid trace-id\",\n\t\t\tdbSpans: []dbmodel.Span{{\n\t\t\t\tTraceID:      dbmodel.TraceID(\"0123456789abcdef0123456789abcdef\"),\n\t\t\t\tSpanID:       dbmodel.SpanID(\"0123456789abcdef\"),\n\t\t\t\tParentSpanID: dbmodel.SpanID(\"invalid-parent-id\"),\n\t\t\t}},\n\t\t\terr: \"encoding/hex: invalid byte: U+0069 'i'\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t_, err := FromDBModel(test.dbSpans)\n\t\t\trequire.ErrorContains(t, err, test.err)\n\t\t})\n\t}\n}\n\nfunc TestSetParentId(t *testing.T) {\n\tparentSpanId := [8]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}\n\ttrace, err := FromDBModel([]dbmodel.Span{{ParentSpanID: getDbSpanIdFromByteArray(parentSpanId)}})\n\trequire.NoError(t, err)\n\tassert.Equal(t, pcommon.SpanID(parentSpanId), trace.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).ParentSpanID())\n}\n\nfunc TestParentIdWhenRefTraceIdIsDifferent(t *testing.T) {\n\ttraceId := getDbTraceIdFromByteArray([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})\n\trefTraceId := getDbTraceIdFromByteArray([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x81})\n\ttrace, err := FromDBModel([]dbmodel.Span{{TraceID: traceId, References: []dbmodel.Reference{{TraceID: refTraceId}}}})\n\trequire.NoError(t, err)\n\tassert.True(t, trace.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).ParentSpanID().IsEmpty())\n}\n\nfunc TestDbSpanToSpanWithSpanKind(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tspanKind     string\n\t\texpectedKind ptrace.SpanKind\n\t\tdescription  string\n\t}{\n\t\t{\n\t\t\tname:         \"span with client kind\",\n\t\t\tspanKind:     \"client\",\n\t\t\texpectedKind: ptrace.SpanKindClient,\n\t\t\tdescription:  \"Span with client kind should be converted properly\",\n\t\t},\n\t\t{\n\t\t\tname:         \"span with server kind\",\n\t\t\tspanKind:     \"server\",\n\t\t\texpectedKind: ptrace.SpanKindServer,\n\t\t\tdescription:  \"Span with server kind should be converted properly\",\n\t\t},\n\t\t{\n\t\t\tname:         \"span with unspecified kind\",\n\t\t\tspanKind:     \"unknown\",\n\t\t\texpectedKind: ptrace.SpanKindUnspecified,\n\t\t\tdescription:  \"Span with unknown kind should be unspecified\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdbSpan := &dbmodel.Span{\n\t\t\t\tTraceID: dbmodel.TraceID(\"0123456789abcdef0123456789abcdef\"),\n\t\t\t\tSpanID:  dbmodel.SpanID(\"0123456789abcdef\"),\n\t\t\t\tTags: []dbmodel.KeyValue{\n\t\t\t\t\t{Key: model.SpanKindKey, Value: tt.spanKind, Type: dbmodel.StringType},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tspan := ptrace.NewSpan()\n\t\t\terr := dbSpanToSpan(dbSpan, span)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tt.expectedKind, span.Kind(), tt.description)\n\t\t\t// Verify span kind attribute was removed\n\t\t\t_, exists := span.Attributes().Get(model.SpanKindKey)\n\t\t\tassert.False(t, exists, \"Span kind attribute should be removed\")\n\t\t})\n\t}\n}\n\nfunc TestDbProcessToResource(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tprocess       dbmodel.Process\n\t\texpectedAttrs map[string]any\n\t\tdescription   string\n\t}{\n\t\t{\n\t\t\tname: \"process with service name and tags\",\n\t\t\tprocess: dbmodel.Process{\n\t\t\t\tServiceName: \"test-service\",\n\t\t\t\tTags: []dbmodel.KeyValue{\n\t\t\t\t\t{Key: \"key1\", Value: \"value1\", Type: dbmodel.StringType},\n\t\t\t\t\t{Key: \"key2\", Value: \"value2\", Type: dbmodel.StringType},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedAttrs: map[string]any{\n\t\t\t\tstring(conventions.ServiceNameKey): \"test-service\",\n\t\t\t\t\"key1\":                             \"value1\",\n\t\t\t\t\"key2\":                             \"value2\",\n\t\t\t},\n\t\t\tdescription: \"Process with service name should trigger first branch\",\n\t\t},\n\t\t{\n\t\t\tname: \"process with empty service name and tags\",\n\t\t\tprocess: dbmodel.Process{\n\t\t\t\tServiceName: \"\",\n\t\t\t\tTags: []dbmodel.KeyValue{\n\t\t\t\t\t{Key: \"key1\", Value: \"value1\", Type: dbmodel.StringType},\n\t\t\t\t\t{Key: \"key2\", Value: \"value2\", Type: dbmodel.StringType},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedAttrs: map[string]any{\n\t\t\t\t\"key1\": \"value1\",\n\t\t\t\t\"key2\": \"value2\",\n\t\t\t},\n\t\t\tdescription: \"Process with empty service name should trigger else branch\",\n\t\t},\n\t\t{\n\t\t\tname: \"process with noServiceName and tags\",\n\t\t\tprocess: dbmodel.Process{\n\t\t\t\tServiceName: noServiceName,\n\t\t\t\tTags: []dbmodel.KeyValue{\n\t\t\t\t\t{Key: \"key1\", Value: \"value1\", Type: dbmodel.StringType},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedAttrs: map[string]any{\n\t\t\t\t\"key1\": \"value1\",\n\t\t\t},\n\t\t\tdescription: \"Process with noServiceName should trigger else branch\",\n\t\t},\n\t\t{\n\t\t\tname: \"process with empty service name and no tags\",\n\t\t\tprocess: dbmodel.Process{\n\t\t\t\tServiceName: \"\",\n\t\t\t\tTags:        nil,\n\t\t\t},\n\t\t\texpectedAttrs: map[string]any{},\n\t\t\tdescription:   \"Process with no service name and no tags should return early\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresource := pcommon.NewResource()\n\t\t\tdbProcessToResource(tt.process, resource)\n\n\t\t\t// Instead of comparing the entire map structure, compare individual attributes\n\t\t\tattrs := resource.Attributes()\n\t\t\tfor key, expectedValue := range tt.expectedAttrs {\n\t\t\t\tactualValue, exists := attrs.Get(key)\n\t\t\t\tassert.True(t, exists, \"Expected attribute %s to exist\", key)\n\n\t\t\t\t// Fixed: Replace switch with if-then as suggested by linter\n\t\t\t\tif v, ok := expectedValue.(string); ok {\n\t\t\t\t\tassert.Equal(t, v, actualValue.Str(), \"Attribute %s value mismatch\", key)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Verify the total number of attributes matches\n\t\t\tassert.Equal(t, len(tt.expectedAttrs), attrs.Len(), \"Total attribute count mismatch\")\n\t\t})\n\t}\n}\n\nfunc TestGetTraceStateFromAttrs(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\tattrs              map[string]any\n\t\texpectedTraceState string\n\t\texpectedAttrCount  int\n\t\tdescription        string\n\t}{\n\t\t{\n\t\t\tname: \"attrs with w3c trace state\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagW3CTraceState: \"vendor1=value1,vendor2=value2\",\n\t\t\t\t\"other-attr\":     \"other-value\",\n\t\t\t},\n\t\t\texpectedTraceState: \"vendor1=value1,vendor2=value2\",\n\t\t\texpectedAttrCount:  1,\n\t\t\tdescription:        \"Should extract and remove W3C trace state\",\n\t\t},\n\t\t{\n\t\t\tname: \"attrs without w3c trace state\",\n\t\t\tattrs: map[string]any{\n\t\t\t\t\"other-attr\": \"other-value\",\n\t\t\t},\n\t\t\texpectedTraceState: \"\",\n\t\t\texpectedAttrCount:  1,\n\t\t\tdescription:        \"Should return empty string when no trace state\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tattrs := pcommon.NewMap()\n\t\t\trequire.NoError(t, attrs.FromRaw(tt.attrs))\n\t\t\ttraceState := getTraceStateFromAttrs(attrs)\n\t\t\tassert.Equal(t, tt.expectedTraceState, traceState, tt.description)\n\t\t\tassert.Equal(t, tt.expectedAttrCount, attrs.Len(), \"Attribute count should match expected\")\n\t\t})\n\t}\n}\n\nfunc TestSetInternalSpanStatus(t *testing.T) {\n\tokStatus := ptrace.NewStatus()\n\tokStatus.SetCode(ptrace.StatusCodeOk)\n\terrorStatus := ptrace.NewStatus()\n\terrorStatus.SetCode(ptrace.StatusCodeError)\n\terrorStatusWithMessage := ptrace.NewStatus()\n\terrorStatusWithMessage.SetCode(ptrace.StatusCodeError)\n\terrorStatusWithMessage.SetMessage(\"Error: Invalid argument\")\n\terrorStatusWith404Message := ptrace.NewStatus()\n\terrorStatusWith404Message.SetCode(ptrace.StatusCodeError)\n\terrorStatusWith404Message.SetMessage(\"HTTP 404: Not Found\")\n\n\ttests := []struct {\n\t\tname             string\n\t\tattrs            map[string]any\n\t\tstatus           ptrace.Status\n\t\tkind             ptrace.SpanKind\n\t\tattrsModifiedLen int // Length of attributes map after dropping converted fields\n\t}{\n\t\t{\n\t\t\tname: \"status.code is set as string\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.OtelStatusCode: statusOk,\n\t\t\t},\n\t\t\tstatus:           okStatus,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"status.code, status.message and error tags are set\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.OtelStatusCode:        statusError,\n\t\t\t\tconventions.OtelStatusDescription: \"Error: Invalid argument\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWithMessage,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"http.status_code tag is set as string\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.HTTPResponseStatusCodeKey: \"404\",\n\t\t\t},\n\t\t\tstatus:           errorStatus,\n\t\t\tattrsModifiedLen: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"http.status_code, http.status_message and error tags are set\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.HTTPResponseStatusCodeKey: 404,\n\t\t\t\ttagHTTPStatusMsg:                      \"HTTP 404: Not Found\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWith404Message,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"status.code has precedence over http.status_code.\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.OtelStatusCode:            statusOk,\n\t\t\t\tconventions.HTTPResponseStatusCodeKey: 500,\n\t\t\t\ttagHTTPStatusMsg:                      \"Server Error\",\n\t\t\t},\n\t\t\tstatus:           okStatus,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"status.error has precedence over http.status_error.\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.OtelStatusCode:            statusError,\n\t\t\t\tconventions.HTTPResponseStatusCodeKey: 500,\n\t\t\t\ttagHTTPStatusMsg:                      \"Server Error\",\n\t\t\t},\n\t\t\tstatus:           errorStatus,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"whether tagHttpStatusMsg is set as string\",\n\t\t\tattrs: map[string]any{\n\t\t\t\tconventions.HTTPResponseStatusCodeKey: 404,\n\t\t\t\ttagHTTPStatusMsg:                      \"HTTP 404: Not Found\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWith404Message,\n\t\t\tattrsModifiedLen: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"error tag set and message present\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError:                          true,\n\t\t\t\tconventions.OtelStatusDescription: \"Error: Invalid argument\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWithMessage,\n\t\t\tattrsModifiedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"error tag set and http tag message present\",\n\t\t\tattrs: map[string]any{\n\t\t\t\ttagError:         true,\n\t\t\t\ttagHTTPStatusMsg: \"HTTP 404: Not Found\",\n\t\t\t},\n\t\t\tstatus:           errorStatusWith404Message,\n\t\t\tattrsModifiedLen: 1,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tspan := ptrace.NewSpan()\n\t\t\tspan.SetKind(test.kind)\n\t\t\tstatus := span.Status()\n\t\t\tattrs := pcommon.NewMap()\n\t\t\trequire.NoError(t, attrs.FromRaw(test.attrs))\n\t\t\tsetSpanStatus(attrs, span)\n\t\t\tassert.Equal(t, test.status, status)\n\t\t\tassert.Equal(t, test.attrsModifiedLen, attrs.Len())\n\t\t})\n\t}\n}\n\nfunc TestDBSpanKindToOTELSpanKind(t *testing.T) {\n\ttests := []struct {\n\t\tjSpanKind    string\n\t\totlpSpanKind ptrace.SpanKind\n\t}{\n\t\t{\n\t\t\tjSpanKind:    \"client\",\n\t\t\totlpSpanKind: ptrace.SpanKindClient,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"server\",\n\t\t\totlpSpanKind: ptrace.SpanKindServer,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"producer\",\n\t\t\totlpSpanKind: ptrace.SpanKindProducer,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"consumer\",\n\t\t\totlpSpanKind: ptrace.SpanKindConsumer,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"internal\",\n\t\t\totlpSpanKind: ptrace.SpanKindInternal,\n\t\t},\n\t\t{\n\t\t\tjSpanKind:    \"all-others\",\n\t\t\totlpSpanKind: ptrace.SpanKindUnspecified,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.jSpanKind, func(t *testing.T) {\n\t\t\tassert.Equal(t, test.otlpSpanKind, dbSpanKindToOTELSpanKind(test.jSpanKind))\n\t\t})\n\t}\n}\n\nfunc TestDbSpanKindToOTELSpanKind_DefaultCase(t *testing.T) {\n\tresult := dbSpanKindToOTELSpanKind(\"unknown-span-kind\")\n\tassert.Equal(t, ptrace.SpanKindUnspecified, result)\n\n\tresult = dbSpanKindToOTELSpanKind(\"\")\n\tassert.Equal(t, ptrace.SpanKindUnspecified, result)\n\n\tresult = dbSpanKindToOTELSpanKind(\"invalid\")\n\tassert.Equal(t, ptrace.SpanKindUnspecified, result)\n}\n\nfunc TestSetInternalSpanStatus_DefaultCase(t *testing.T) {\n\tspan := ptrace.NewSpan()\n\tstatus := span.Status()\n\tattrs := pcommon.NewMap()\n\n\tattrs.PutStr(conventions.OtelStatusCode, \"UNKNOWN_STATUS\")\n\n\tsetSpanStatus(attrs, span)\n\n\tassert.Equal(t, ptrace.StatusCodeUnset, status.Code())\n\tassert.Empty(t, status.Message())\n}\n\nfunc TestFromDbModel_Fixtures(t *testing.T) {\n\ttracesData, spansData := loadFixtures(t, 1)\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\texpectedTd, err := unmarshaller.UnmarshalTraces(tracesData)\n\trequire.NoError(t, err)\n\tspans := ToDBModel(expectedTd)\n\tassert.Len(t, spans, 1)\n\ttestSpans(t, spansData, spans[0])\n\tactualTd, err := FromDBModel(spans)\n\trequire.NoError(t, err)\n\ttestTraces(t, tracesData, actualTd)\n}\n\nfunc TestToDbModel_Fixtures_StringTags(t *testing.T) {\n\tspanData, err := os.ReadFile(\"fixtures/es_01_string_tags.json\")\n\trequire.NoError(t, err)\n\tvar dbSpan dbmodel.Span\n\trequire.NoError(t, json.Unmarshal(spanData, &dbSpan))\n\ttd, err := FromDBModel([]dbmodel.Span{dbSpan})\n\trequire.NoError(t, err)\n\texpectedTraces := loadTraces(t, 1)\n\ttestTraces(t, expectedTraces, td)\n}\n\nfunc getDbTraceIdFromByteArray(arr [16]byte) dbmodel.TraceID {\n\treturn dbmodel.TraceID(hex.EncodeToString(arr[:]))\n}\n\nfunc getDbSpanIdFromByteArray(arr [8]byte) dbmodel.SpanID {\n\treturn dbmodel.SpanID(hex.EncodeToString(arr[:]))\n}\n\nfunc BenchmarkProtoBatchToInternalTraces(b *testing.B) {\n\tdata, err := os.ReadFile(\"fixtures.es_01.json\")\n\trequire.NoError(b, err)\n\tvar dbSpan dbmodel.Span\n\terr = json.Unmarshal(data, &dbSpan)\n\trequire.NoError(b, err)\n\tjb := []dbmodel.Span{dbSpan}\n\n\tfor b.Loop() {\n\t\t_, err := FromDBModel(jb)\n\t\tassert.NoError(b, err)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/ids.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"encoding/hex\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n)\n\nfunc convertTraceIDFromDB(dbTraceId dbmodel.TraceID) (pcommon.TraceID, error) {\n\tvar traceId [16]byte\n\ttraceBytes, err := hex.DecodeString(string(dbTraceId))\n\tif err != nil {\n\t\treturn pcommon.TraceID{}, err\n\t}\n\tcopy(traceId[:], traceBytes)\n\treturn traceId, nil\n}\n\nfunc fromDbSpanId(dbSpanId dbmodel.SpanID) (pcommon.SpanID, error) {\n\tvar spanId [8]byte\n\tspanIdBytes, err := hex.DecodeString(string(dbSpanId))\n\tif err != nil {\n\t\treturn pcommon.SpanID{}, err\n\t}\n\tcopy(spanId[:], spanIdBytes)\n\treturn spanId, nil\n}\n\n// TODO extend DB model to support parent span ID directly\nfunc getParentSpanId(dbSpan *dbmodel.Span) dbmodel.SpanID {\n\tvar followsFromRef *dbmodel.Reference\n\tfor i := range dbSpan.References {\n\t\tref := dbSpan.References[i]\n\t\tif ref.TraceID != dbSpan.TraceID {\n\t\t\tcontinue\n\t\t}\n\t\tif ref.RefType == dbmodel.ChildOf {\n\t\t\treturn ref.SpanID\n\t\t}\n\t\tif followsFromRef == nil && ref.RefType == dbmodel.FollowsFrom {\n\t\t\tfollowsFromRef = &ref\n\t\t}\n\t}\n\tif followsFromRef != nil {\n\t\treturn followsFromRef.SpanID\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/reader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"iter\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\n// TraceReader is a wrapper around spanstore.CoreSpanReader which return the output parallel to OTLP Models\ntype TraceReader struct {\n\tspanReader spanstore.CoreSpanReader\n}\n\n// NewTraceReader returns an instance of TraceReader\nfunc NewTraceReader(p spanstore.SpanReaderParams) *TraceReader {\n\treturn &TraceReader{\n\t\tspanReader: spanstore.NewSpanReader(p),\n\t}\n}\n\nfunc (t *TraceReader) GetTraces(ctx context.Context, params ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tdbTraceIds := make([]dbmodel.TraceID, 0, len(params))\n\t\tfor _, id := range params {\n\t\t\tdbTraceIds = append(dbTraceIds, dbmodel.TraceID(id.TraceID.String()))\n\t\t}\n\t\tdbTraces, err := t.spanReader.GetTraces(ctx, dbTraceIds)\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\tfor _, trace := range dbTraces {\n\t\t\ttd, err := FromDBModel(trace.Spans)\n\t\t\tif err != nil {\n\t\t\t\tyield(nil, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !yield([]ptrace.Traces{td}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *TraceReader) GetServices(ctx context.Context) ([]string, error) {\n\treturn t.spanReader.GetServices(ctx)\n}\n\nfunc (t *TraceReader) GetOperations(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error) {\n\tdbOperations, err := t.spanReader.GetOperations(ctx, dbmodel.OperationQueryParameters{\n\t\tServiceName: query.ServiceName,\n\t\tSpanKind:    query.SpanKind,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toperations := make([]tracestore.Operation, 0, len(dbOperations))\n\tfor _, op := range dbOperations {\n\t\toperations = append(operations, tracestore.Operation{\n\t\t\tName:     op.Name,\n\t\t\tSpanKind: op.SpanKind,\n\t\t})\n\t}\n\treturn operations, nil\n}\n\nfunc (t *TraceReader) FindTraces(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\ttraces, err := t.spanReader.FindTraces(ctx, toDBTraceQueryParams(query))\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\tfor _, trace := range traces {\n\t\t\ttd, err := FromDBModel(trace.Spans)\n\t\t\tif err != nil {\n\t\t\t\tyield(nil, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !yield([]ptrace.Traces{td}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *TraceReader) FindTraceIDs(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\treturn func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\ttraceIds, err := t.spanReader.FindTraceIDs(ctx, toDBTraceQueryParams(query))\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\totelTraceIds := make([]tracestore.FoundTraceID, 0, len(traceIds))\n\t\tfor _, traceId := range traceIds {\n\t\t\tdbTraceId, err := convertTraceIDFromDB(traceId)\n\t\t\tif err != nil {\n\t\t\t\tyield(nil, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\totelTraceIds = append(otelTraceIds, tracestore.FoundTraceID{\n\t\t\t\tTraceID: dbTraceId,\n\t\t\t})\n\t\t}\n\t\tyield(otelTraceIds, nil)\n\t}\n}\n\nfunc toDBTraceQueryParams(query tracestore.TraceQueryParams) dbmodel.TraceQueryParameters {\n\ttags := make(map[string]string)\n\tfor key, val := range query.Attributes.All() {\n\t\ttags[key] = val.AsString()\n\t}\n\treturn dbmodel.TraceQueryParameters{\n\t\tServiceName:   query.ServiceName,\n\t\tOperationName: query.OperationName,\n\t\tStartTimeMin:  query.StartTimeMin,\n\t\tStartTimeMax:  query.StartTimeMax,\n\t\tTags:          tags,\n\t\tNumTraces:     query.SearchDepth,\n\t\tDurationMin:   query.DurationMin,\n\t\tDurationMax:   query.DurationMax,\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/reader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"iter\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nfunc TestTraceReader_GetServices(t *testing.T) {\n\tcoreReader := &mocks.CoreSpanReader{}\n\treader := TraceReader{spanReader: coreReader}\n\tservices := []string{\"service1\", \"service2\"}\n\tcoreReader.On(\"GetServices\", mock.Anything).Return(services, nil)\n\tactual, err := reader.GetServices(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, services, actual)\n}\n\nfunc TestTraceReader_GetOperations(t *testing.T) {\n\tcoreReader := &mocks.CoreSpanReader{}\n\treader := TraceReader{spanReader: coreReader}\n\toperations := []dbmodel.Operation{\n\t\t{\n\t\t\tName:     \"op-1\",\n\t\t\tSpanKind: \"kind--1\",\n\t\t},\n\t\t{\n\t\t\tName:     \"op-2\",\n\t\t\tSpanKind: \"kind--2\",\n\t\t},\n\t}\n\tcoreReader.On(\"GetOperations\", mock.Anything, mock.Anything).Return(operations, nil)\n\texpected := []tracestore.Operation{\n\t\t{\n\t\t\tName:     \"op-1\",\n\t\t\tSpanKind: \"kind--1\",\n\t\t},\n\t\t{\n\t\t\tName:     \"op-2\",\n\t\t\tSpanKind: \"kind--2\",\n\t\t},\n\t}\n\tactual, err := reader.GetOperations(context.Background(), tracestore.OperationQueryParams{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, expected, actual)\n}\n\nfunc TestTraceReader_GetOperations_Error(t *testing.T) {\n\tcoreReader := &mocks.CoreSpanReader{}\n\treader := TraceReader{spanReader: coreReader}\n\tcoreReader.On(\"GetOperations\", mock.Anything, mock.Anything).Return(nil, errors.New(\"error\"))\n\toperations, err := reader.GetOperations(context.Background(), tracestore.OperationQueryParams{})\n\trequire.EqualError(t, err, \"error\")\n\trequire.Nil(t, operations)\n}\n\nfunc TestTraceReader_GetTraces(t *testing.T) {\n\tcoreReader := &mocks.CoreSpanReader{}\n\treader := TraceReader{spanReader: coreReader}\n\ttracesStr, spanStr := loadFixtures(t, 1)\n\tvar span dbmodel.Span\n\trequire.NoError(t, json.Unmarshal(spanStr, &span))\n\tdbTrace := dbmodel.Trace{Spans: []dbmodel.Span{span}}\n\tspan.TraceID = \"00000000000000020000000000000000\"\n\tdbTrace2 := dbmodel.Trace{Spans: []dbmodel.Span{span}}\n\tcoreReader.On(\"GetTraces\", mock.Anything, mock.Anything).Return([]dbmodel.Trace{dbTrace, dbTrace2}, nil)\n\ttraces := reader.GetTraces(context.Background(), tracestore.GetTraceParams{})\n\tfor td, err := range traces {\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, td, 1)\n\t\ttestTraces(t, tracesStr, td[0])\n\t\tbreak\n\t}\n}\n\nfunc testTraceReaderGetTracesAndFindTracesErrors(t *testing.T, fxnName string, actualTraces func(r TraceReader) iter.Seq2[[]ptrace.Traces, error]) {\n\ttests := []struct {\n\t\tname        string\n\t\texpectedErr string\n\t\tmockFxn     func(m *mocks.CoreSpanReader)\n\t}{\n\t\t{\n\t\t\tname:        \"some error from core reader\",\n\t\t\texpectedErr: \"some error\",\n\t\t\tmockFxn: func(m *mocks.CoreSpanReader) {\n\t\t\t\tm.On(fxnName, mock.Anything, mock.Anything).Return(nil, errors.New(\"some error\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"conversion error\",\n\t\t\tmockFxn: func(m *mocks.CoreSpanReader) {\n\t\t\t\tdbTraces := []dbmodel.Trace{\n\t\t\t\t\t{\n\t\t\t\t\t\tSpans: []dbmodel.Span{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tTraceID: \"wrong-trace-id\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tm.On(fxnName, mock.Anything, mock.Anything).Return(dbTraces, nil)\n\t\t\t},\n\t\t\texpectedErr: \"encoding/hex: invalid byte: U+0077 'w'\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcoreReader := &mocks.CoreSpanReader{}\n\t\t\treader := TraceReader{spanReader: coreReader}\n\t\t\ttt.mockFxn(coreReader)\n\t\t\ttraces := actualTraces(reader)\n\t\t\tfor trace, err := range traces {\n\t\t\t\trequire.Nil(t, trace)\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_GetTraces_Errors(t *testing.T) {\n\ttestTraceReaderGetTracesAndFindTracesErrors(t, \"GetTraces\", func(r TraceReader) iter.Seq2[[]ptrace.Traces, error] {\n\t\treturn r.GetTraces(context.Background(), tracestore.GetTraceParams{})\n\t})\n}\n\nfunc TestTraceReader_FindTraces(t *testing.T) {\n\tcoreReader := &mocks.CoreSpanReader{}\n\treader := TraceReader{spanReader: coreReader}\n\ttracesStr, spanStr := loadFixtures(t, 1)\n\tvar span dbmodel.Span\n\trequire.NoError(t, json.Unmarshal(spanStr, &span))\n\tdbTrace := dbmodel.Trace{Spans: []dbmodel.Span{span}}\n\tspan.TraceID = \"00000000000000020000000000000000\"\n\tdbTrace2 := dbmodel.Trace{Spans: []dbmodel.Span{span}}\n\tcoreReader.On(\"FindTraces\", mock.Anything, mock.Anything).Return([]dbmodel.Trace{dbTrace, dbTrace2}, nil)\n\ttraces := reader.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t})\n\tfor td, err := range traces {\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, td, 1)\n\t\ttestTraces(t, tracesStr, td[0])\n\t\tbreak\n\t}\n}\n\nfunc TestTraceReader_FindTraces_Errors(t *testing.T) {\n\ttestTraceReaderGetTracesAndFindTracesErrors(t, \"FindTraces\", func(r TraceReader) iter.Seq2[[]ptrace.Traces, error] {\n\t\treturn r.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\t\tAttributes: pcommon.NewMap(),\n\t\t})\n\t})\n}\n\nfunc TestTraceReader_FindTraceIDs(t *testing.T) {\n\tcoreReader := &mocks.CoreSpanReader{}\n\treader := TraceReader{spanReader: coreReader}\n\tdbTraceIDs := []dbmodel.TraceID{\n\t\t\"00000000000000010000000000000000\",\n\t\t\"00000000000000020000000000000000\",\n\t\t\"00000000000000030000000000000000\",\n\t}\n\texpected := make([]tracestore.FoundTraceID, 0, len(dbTraceIDs))\n\tfor _, dbTraceID := range dbTraceIDs {\n\t\texpected = append(expected, fromDBTraceId(t, dbTraceID))\n\t}\n\tcoreReader.On(\"FindTraceIDs\", mock.Anything, mock.Anything).Return(dbTraceIDs, nil)\n\tfor traceIds, err := range reader.FindTraceIDs(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes: pcommon.NewMap(),\n\t}) {\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, expected, traceIds)\n\t}\n}\n\nfunc TestTraceReader_FindTraceIDs_Error(t *testing.T) {\n\ttests := []struct {\n\t\tname                   string\n\t\terrFromCoreReader      error\n\t\ttraceIdsFromCoreReader []dbmodel.TraceID\n\t\texpectedErr            string\n\t}{\n\t\t{\n\t\t\tname:              \"some error from core reader\",\n\t\t\terrFromCoreReader: errors.New(\"some error from core reader\"),\n\t\t\texpectedErr:       \"some error from core reader\",\n\t\t},\n\t\t{\n\t\t\tname:                   \"wrong trace id sent from core reader\",\n\t\t\ttraceIdsFromCoreReader: []dbmodel.TraceID{\"wrong-id\"},\n\t\t\texpectedErr:            \"encoding/hex: invalid byte: U+0077 'w'\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcoreReader := &mocks.CoreSpanReader{}\n\t\t\tattrs := pcommon.NewMap()\n\t\t\tattrs.PutStr(\"key1\", \"val1\")\n\t\t\tts := time.Now()\n\t\t\ttraceQueryParams := tracestore.TraceQueryParams{\n\t\t\t\tAttributes:    attrs,\n\t\t\t\tStartTimeMin:  ts,\n\t\t\t\tServiceName:   \"testing-service-name\",\n\t\t\t\tOperationName: \"testing-operation-name\",\n\t\t\t\tStartTimeMax:  ts.Add(1 * time.Hour),\n\t\t\t\tDurationMin:   1 * time.Hour,\n\t\t\t\tDurationMax:   1 * time.Hour,\n\t\t\t\tSearchDepth:   10,\n\t\t\t}\n\t\t\tdbTraceQueryParams := dbmodel.TraceQueryParameters{\n\t\t\t\tTags:          map[string]string{\"key1\": \"val1\"},\n\t\t\t\tStartTimeMin:  ts,\n\t\t\t\tServiceName:   \"testing-service-name\",\n\t\t\t\tOperationName: \"testing-operation-name\",\n\t\t\t\tStartTimeMax:  ts.Add(1 * time.Hour),\n\t\t\t\tDurationMin:   1 * time.Hour,\n\t\t\t\tDurationMax:   1 * time.Hour,\n\t\t\t\tNumTraces:     10,\n\t\t\t}\n\t\t\tcoreReader.On(\"FindTraceIDs\", mock.Anything, dbTraceQueryParams).Return(test.traceIdsFromCoreReader, test.errFromCoreReader)\n\t\t\treader := TraceReader{spanReader: coreReader}\n\t\t\tfor traceIds, err := range reader.FindTraceIDs(context.Background(), traceQueryParams) {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t\t\trequire.Nil(t, traceIds)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_NewTraceReader(t *testing.T) {\n\treader := NewTraceReader(spanstore.SpanReaderParams{\n\t\tLogger: zap.NewNop(),\n\t})\n\tassert.IsType(t, &spanstore.SpanReader{}, reader.spanReader)\n}\n\nfunc fromDBTraceId(t *testing.T, traceID dbmodel.TraceID) tracestore.FoundTraceID {\n\ttraceId, err := convertTraceIDFromDB(traceID)\n\trequire.NoError(t, err)\n\treturn tracestore.FoundTraceID{\n\t\tTraceID: traceId,\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/to_dbmodel.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/traces_to_jaegerproto.go\n\npackage tracestore\n\nimport (\n\t\"encoding/hex\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\tconventions \"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nconst (\n\tnoServiceName    = \"OTLPResourceNoServiceName\"\n\teventNameAttr    = \"event\"\n\tstatusError      = \"ERROR\"\n\tstatusOk         = \"OK\"\n\ttagW3CTraceState = \"w3c.tracestate\"\n\ttagHTTPStatusMsg = \"http.status_message\"\n\ttagError         = \"error\"\n)\n\n// ToDBModel translates internal trace data into the DB Spans.\n// Returns slice of translated DB Spans and error if translation failed.\nfunc ToDBModel(td ptrace.Traces) []dbmodel.Span {\n\tresourceSpans := td.ResourceSpans()\n\n\tif resourceSpans.Len() == 0 {\n\t\treturn nil\n\t}\n\n\tbatches := make([]dbmodel.Span, 0, resourceSpans.Len())\n\tfor i := 0; i < resourceSpans.Len(); i++ {\n\t\trs := resourceSpans.At(i)\n\t\tbatch := resourceSpansToDbSpans(rs)\n\t\tif batch != nil {\n\t\t\tbatches = append(batches, batch...)\n\t\t}\n\t}\n\n\treturn batches\n}\n\nfunc resourceSpansToDbSpans(resourceSpans ptrace.ResourceSpans) []dbmodel.Span {\n\tresource := resourceSpans.Resource()\n\tscopeSpans := resourceSpans.ScopeSpans()\n\n\tif scopeSpans.Len() == 0 {\n\t\treturn []dbmodel.Span{}\n\t}\n\n\tprocess := resourceToDbProcess(resource)\n\n\t// Approximate the number of the spans as the number of the spans in the first\n\t// instrumentation library info.\n\tdbSpans := make([]dbmodel.Span, 0, scopeSpans.At(0).Spans().Len())\n\n\tfor _, scopeSpan := range scopeSpans.All() {\n\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\tdbSpan := spanToDbSpan(span, scopeSpan.Scope(), process)\n\t\t\tdbSpans = append(dbSpans, dbSpan)\n\t\t}\n\t}\n\n\treturn dbSpans\n}\n\nfunc resourceToDbProcess(resource pcommon.Resource) dbmodel.Process {\n\tprocess := dbmodel.Process{}\n\tattrs := resource.Attributes()\n\tif attrs.Len() == 0 {\n\t\tprocess.ServiceName = noServiceName\n\t\treturn process\n\t}\n\ttags := make([]dbmodel.KeyValue, 0, attrs.Len())\n\tfor key, attr := range attrs.All() {\n\t\tif key == conventions.ServiceNameKey {\n\t\t\tprocess.ServiceName = attr.AsString()\n\t\t\tcontinue\n\t\t}\n\t\ttags = append(tags, attributeToDbTag(key, attr))\n\t}\n\tprocess.Tags = tags\n\treturn process\n}\n\nfunc appendTagsFromAttributes(dest []dbmodel.KeyValue, attrs pcommon.Map) []dbmodel.KeyValue {\n\tfor key, attr := range attrs.All() {\n\t\tdest = append(dest, attributeToDbTag(key, attr))\n\t}\n\treturn dest\n}\n\nfunc attributeToDbTag(key string, attr pcommon.Value) dbmodel.KeyValue {\n\tvar tag dbmodel.KeyValue\n\tswitch attr.Type() {\n\tcase pcommon.ValueTypeBytes:\n\t\ttag = dbmodel.KeyValue{Key: key, Value: hex.EncodeToString(attr.Bytes().AsRaw())}\n\tcase pcommon.ValueTypeMap, pcommon.ValueTypeSlice:\n\t\ttag = dbmodel.KeyValue{Key: key, Value: attr.AsString()}\n\tdefault:\n\t\ttag = dbmodel.KeyValue{Key: key, Value: attr.AsRaw()}\n\t}\n\tswitch attr.Type() {\n\tcase pcommon.ValueTypeInt:\n\t\ttag.Type = dbmodel.Int64Type\n\tcase pcommon.ValueTypeBool:\n\t\ttag.Type = dbmodel.BoolType\n\tcase pcommon.ValueTypeDouble:\n\t\ttag.Type = dbmodel.Float64Type\n\tcase pcommon.ValueTypeBytes:\n\t\ttag.Type = dbmodel.BinaryType\n\tdefault:\n\t\ttag.Type = dbmodel.StringType\n\t}\n\treturn tag\n}\n\nfunc spanToDbSpan(span ptrace.Span, libraryTags pcommon.InstrumentationScope, process dbmodel.Process) dbmodel.Span {\n\ttraceID := dbmodel.TraceID(span.TraceID().String())\n\tparentSpanID := dbmodel.SpanID(span.ParentSpanID().String())\n\tstartTime := span.StartTimestamp().AsTime()\n\treturn dbmodel.Span{\n\t\tTraceID:         traceID,\n\t\tSpanID:          dbmodel.SpanID(span.SpanID().String()),\n\t\tOperationName:   span.Name(),\n\t\tReferences:      linksToDbSpanRefs(span.Links(), parentSpanID, traceID),\n\t\tStartTime:       model.TimeAsEpochMicroseconds(startTime),\n\t\tStartTimeMillis: model.TimeAsEpochMicroseconds(startTime) / 1000,\n\t\tDuration:        model.DurationAsMicroseconds(span.EndTimestamp().AsTime().Sub(startTime)),\n\t\tTags:            getDbSpanTags(span, libraryTags),\n\t\tLogs:            spanEventsToDbSpanLogs(span.Events()),\n\t\tProcess:         process,\n\t\tFlags:           span.Flags(),\n\t}\n}\n\nfunc getDbSpanTags(span ptrace.Span, scope pcommon.InstrumentationScope) []dbmodel.KeyValue {\n\tvar spanKindTag, statusCodeTag, statusMsgTag dbmodel.KeyValue\n\tvar spanKindTagFound, statusCodeTagFound, statusMsgTagFound bool\n\n\tlibraryTags, libraryTagsFound := getTagsFromInstrumentationLibrary(scope)\n\n\ttagsCount := span.Attributes().Len() + len(libraryTags)\n\n\tspanKindTag, spanKindTagFound = getTagFromSpanKind(span.Kind())\n\tif spanKindTagFound {\n\t\ttagsCount++\n\t}\n\tstatus := span.Status()\n\tstatusCodeTag, statusCodeTagFound = getTagFromStatusCode(status.Code())\n\tif statusCodeTagFound {\n\t\ttagsCount++\n\t}\n\n\tstatusMsgTag, statusMsgTagFound = getTagFromStatusMsg(status.Message())\n\tif statusMsgTagFound {\n\t\ttagsCount++\n\t}\n\n\ttraceStateTags, traceStateTagsFound := getTagsFromTraceState(span.TraceState().AsRaw())\n\tif traceStateTagsFound {\n\t\ttagsCount += len(traceStateTags)\n\t}\n\n\tif tagsCount == 0 {\n\t\treturn nil\n\t}\n\n\ttags := make([]dbmodel.KeyValue, 0, tagsCount)\n\tif libraryTagsFound {\n\t\ttags = append(tags, libraryTags...)\n\t}\n\ttags = appendTagsFromAttributes(tags, span.Attributes())\n\tif spanKindTagFound {\n\t\ttags = append(tags, spanKindTag)\n\t}\n\tif statusCodeTagFound {\n\t\ttags = append(tags, statusCodeTag)\n\t}\n\tif statusMsgTagFound {\n\t\ttags = append(tags, statusMsgTag)\n\t}\n\tif traceStateTagsFound {\n\t\ttags = append(tags, traceStateTags...)\n\t}\n\treturn tags\n}\n\n// linksToDbSpanRefs constructs jaeger span references based on parent span ID and span links.\n// The parent span ID is used to add a CHILD_OF reference, _unless_ it is referenced from one of the links.\nfunc linksToDbSpanRefs(links ptrace.SpanLinkSlice, parentSpanID dbmodel.SpanID, traceID dbmodel.TraceID) []dbmodel.Reference {\n\trefsCount := links.Len()\n\tif parentSpanID != \"\" {\n\t\trefsCount++\n\t}\n\n\tif refsCount == 0 {\n\t\treturn nil\n\t}\n\n\trefs := make([]dbmodel.Reference, 0, refsCount)\n\n\t// Put parent span ID at the first place because usually backends look for it\n\t// as the first CHILD_OF item in the model.SpanRef slice.\n\tif parentSpanID != \"\" {\n\t\trefs = append(refs, dbmodel.Reference{\n\t\t\tTraceID: traceID,\n\t\t\tSpanID:  parentSpanID,\n\t\t\tRefType: dbmodel.ChildOf,\n\t\t})\n\t}\n\n\tfor i := 0; i < links.Len(); i++ {\n\t\tlink := links.At(i)\n\t\tlinkTraceID := dbmodel.TraceID(link.TraceID().String())\n\t\tlinkSpanID := dbmodel.SpanID(link.SpanID().String())\n\t\tlinkRefType := refTypeFromLink(link)\n\t\tif parentSpanID != \"\" && linkTraceID == traceID && linkSpanID == parentSpanID {\n\t\t\t// We already added a reference to this span, but maybe with the wrong type, so override.\n\t\t\trefs[0].RefType = linkRefType\n\t\t\tcontinue\n\t\t}\n\t\trefs = append(refs, dbmodel.Reference{\n\t\t\tTraceID: linkTraceID,\n\t\t\tSpanID:  linkSpanID,\n\t\t\tRefType: linkRefType,\n\t\t})\n\t}\n\n\treturn refs\n}\n\nfunc spanEventsToDbSpanLogs(events ptrace.SpanEventSlice) []dbmodel.Log {\n\tif events.Len() == 0 {\n\t\treturn nil\n\t}\n\n\tlogs := make([]dbmodel.Log, 0, events.Len())\n\tfor i := 0; i < events.Len(); i++ {\n\t\tevent := events.At(i)\n\t\tfields := make([]dbmodel.KeyValue, 0, event.Attributes().Len()+1)\n\t\t_, eventAttrFound := event.Attributes().Get(eventNameAttr)\n\t\tif event.Name() != \"\" && !eventAttrFound {\n\t\t\tfields = append(fields, dbmodel.KeyValue{\n\t\t\t\tKey:   eventNameAttr,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: event.Name(),\n\t\t\t})\n\t\t}\n\t\tfields = appendTagsFromAttributes(fields, event.Attributes())\n\t\tlogs = append(logs, dbmodel.Log{\n\t\t\tTimestamp: model.TimeAsEpochMicroseconds(event.Timestamp().AsTime()),\n\t\t\tFields:    fields,\n\t\t})\n\t}\n\n\treturn logs\n}\n\nfunc getTagFromSpanKind(spanKind ptrace.SpanKind) (dbmodel.KeyValue, bool) {\n\tvar tagStr string\n\tswitch spanKind {\n\tcase ptrace.SpanKindClient:\n\t\ttagStr = string(model.SpanKindClient)\n\tcase ptrace.SpanKindServer:\n\t\ttagStr = string(model.SpanKindServer)\n\tcase ptrace.SpanKindProducer:\n\t\ttagStr = string(model.SpanKindProducer)\n\tcase ptrace.SpanKindConsumer:\n\t\ttagStr = string(model.SpanKindConsumer)\n\tcase ptrace.SpanKindInternal:\n\t\ttagStr = string(model.SpanKindInternal)\n\tdefault:\n\t\treturn dbmodel.KeyValue{}, false\n\t}\n\n\treturn dbmodel.KeyValue{\n\t\tKey:   model.SpanKindKey,\n\t\tType:  dbmodel.StringType,\n\t\tValue: tagStr,\n\t}, true\n}\n\nfunc getTagFromStatusCode(statusCode ptrace.StatusCode) (dbmodel.KeyValue, bool) {\n\tswitch statusCode {\n\tcase ptrace.StatusCodeOk:\n\t\treturn dbmodel.KeyValue{\n\t\t\tKey:   conventions.OtelStatusCode,\n\t\t\tType:  dbmodel.StringType,\n\t\t\tValue: statusOk,\n\t\t}, true\n\tcase ptrace.StatusCodeError:\n\t\t// For backward compatibility, we also include the error tag\n\t\t// which was previously used in the test fixtures\n\t\treturn dbmodel.KeyValue{\n\t\t\tKey:   \"error\",\n\t\t\tType:  dbmodel.BoolType,\n\t\t\tValue: true,\n\t\t}, true\n\tdefault:\n\t\treturn dbmodel.KeyValue{}, false\n\t}\n}\n\nfunc getTagFromStatusMsg(statusMsg string) (dbmodel.KeyValue, bool) {\n\tif statusMsg == \"\" {\n\t\treturn dbmodel.KeyValue{}, false\n\t}\n\treturn dbmodel.KeyValue{\n\t\tKey:   conventions.OtelStatusDescription,\n\t\tType:  dbmodel.StringType,\n\t\tValue: statusMsg,\n\t}, true\n}\n\nfunc getTagsFromTraceState(traceState string) ([]dbmodel.KeyValue, bool) {\n\tvar keyValues []dbmodel.KeyValue\n\texists := traceState != \"\"\n\tif exists {\n\t\t// TODO Bring this inline with solution for jaegertracing/jaeger-client-java #702 once available\n\t\tkv := dbmodel.KeyValue{\n\t\t\tKey:   tagW3CTraceState,\n\t\t\tValue: traceState,\n\t\t\tType:  dbmodel.StringType,\n\t\t}\n\t\tkeyValues = append(keyValues, kv)\n\t}\n\treturn keyValues, exists\n}\n\nfunc getTagsFromInstrumentationLibrary(il pcommon.InstrumentationScope) ([]dbmodel.KeyValue, bool) {\n\tvar keyValues []dbmodel.KeyValue\n\tif ilName := il.Name(); ilName != \"\" {\n\t\tkv := dbmodel.KeyValue{\n\t\t\tKey:   conventions.AttributeOtelScopeName,\n\t\t\tType:  dbmodel.StringType,\n\t\t\tValue: ilName,\n\t\t}\n\t\tkeyValues = append(keyValues, kv)\n\t}\n\tif ilVersion := il.Version(); ilVersion != \"\" {\n\t\tkv := dbmodel.KeyValue{\n\t\t\tKey:   conventions.AttributeOtelScopeVersion,\n\t\t\tType:  dbmodel.StringType,\n\t\t\tValue: ilVersion,\n\t\t}\n\t\tkeyValues = append(keyValues, kv)\n\t}\n\treturn keyValues, len(keyValues) > 0\n}\n\nfunc refTypeFromLink(link ptrace.SpanLink) dbmodel.ReferenceType {\n\trefTypeAttr, ok := link.Attributes().Get(conventions.AttributeOpentracingRefType)\n\tif !ok {\n\t\treturn dbmodel.FollowsFrom\n\t}\n\treturn strToDbSpanRefType(refTypeAttr.Str())\n}\n\nfunc strToDbSpanRefType(attr string) dbmodel.ReferenceType {\n\tif attr == conventions.AttributeOpentracingRefTypeChildOf {\n\t\treturn dbmodel.ChildOf\n\t}\n\t// There are only 2 types of SpanRefType we assume that everything\n\t// that's not a model.ChildOf is a model.FollowsFrom\n\treturn dbmodel.FollowsFrom\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/to_dbmodel_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// Copyright The OpenTelemetry Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Code originally copied from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/e49500a9b68447cbbe237fa29526ba99e4963f39/pkg/translator/jaeger/traces_to_jaegerproto_test.go\n\npackage tracestore\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel\"\n\tconventions \"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n)\n\nfunc TestGetTagFromStatusCode(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcode ptrace.StatusCode\n\t\ttag  dbmodel.KeyValue\n\t}{\n\t\t{\n\t\t\tname: \"ok\",\n\t\t\tcode: ptrace.StatusCodeOk,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   conventions.OtelStatusCode,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: statusOk,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\tcode: ptrace.StatusCodeError,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   tagError,\n\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot, ok := getTagFromStatusCode(test.code)\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, test.tag, got)\n\t\t})\n\t}\n}\n\nfunc TestGetTagFromSpanKind(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tkind ptrace.SpanKind\n\t\ttag  dbmodel.KeyValue\n\t\tok   bool\n\t}{\n\t\t{\n\t\t\tname: \"unspecified\",\n\t\t\tkind: ptrace.SpanKindUnspecified,\n\t\t\ttag:  dbmodel.KeyValue{},\n\t\t\tok:   false,\n\t\t},\n\t\t{\n\t\t\tname: \"client\",\n\t\t\tkind: ptrace.SpanKindClient,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   model.SpanKindKey,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: string(model.SpanKindClient),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t\t{\n\t\t\tname: \"server\",\n\t\t\tkind: ptrace.SpanKindServer,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   model.SpanKindKey,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: string(model.SpanKindServer),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t\t{\n\t\t\tname: \"producer\",\n\t\t\tkind: ptrace.SpanKindProducer,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   model.SpanKindKey,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: string(model.SpanKindProducer),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t\t{\n\t\t\tname: \"consumer\",\n\t\t\tkind: ptrace.SpanKindConsumer,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   model.SpanKindKey,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: string(model.SpanKindConsumer),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t\t{\n\t\t\tname: \"internal\",\n\t\t\tkind: ptrace.SpanKindInternal,\n\t\t\ttag: dbmodel.KeyValue{\n\t\t\t\tKey:   model.SpanKindKey,\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\tValue: string(model.SpanKindInternal),\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot, ok := getTagFromSpanKind(test.kind)\n\t\t\tassert.Equal(t, test.ok, ok)\n\t\t\tassert.Equal(t, test.tag, got)\n\t\t})\n\t}\n}\n\nfunc TestLinksToDbSpanRefs(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tsetupSpan       func() ptrace.Span\n\t\tsetupLinks      func(ptrace.SpanLinkSlice)\n\t\texpectedRefs    int\n\t\texpectedRefType dbmodel.ReferenceType\n\t\tdescription     string\n\t}{\n\t\t{\n\t\t\tname: \"empty links with follows from attribute\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\treturn scopeSpans.Spans().AppendEmpty()\n\t\t\t},\n\t\t\tsetupLinks: func(links ptrace.SpanLinkSlice) {\n\t\t\t\tlink := links.AppendEmpty()\n\t\t\t\tlink.Attributes().PutStr(\"testing-key\", \"testing-inputValue\")\n\t\t\t\tlink.Attributes().PutStr(conventions.AttributeOpentracingRefType, conventions.AttributeOpentracingRefTypeFollowsFrom)\n\t\t\t},\n\t\t\texpectedRefs:    1,\n\t\t\texpectedRefType: dbmodel.FollowsFrom,\n\t\t\tdescription:     \"Links with explicit follows-from reference type\",\n\t\t},\n\t\t{\n\t\t\tname: \"links without ref type defaults to FollowsFrom\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\treturn scopeSpans.Spans().AppendEmpty()\n\t\t\t},\n\t\t\tsetupLinks: func(links ptrace.SpanLinkSlice) {\n\t\t\t\tlink := links.AppendEmpty()\n\t\t\t\tlink.Attributes().PutStr(\"testing-key\", \"testing-inputValue\")\n\t\t\t},\n\t\t\texpectedRefs:    1,\n\t\t\texpectedRefType: dbmodel.FollowsFrom,\n\t\t\tdescription:     \"Links without reference type should default to FollowsFrom\",\n\t\t},\n\t\t{\n\t\t\tname: \"parent reference with follows from link\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tresourceSpans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := resourceSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := scopeSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})\n\t\t\t\tspan.SetSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})\n\t\t\t\tspan.SetParentSpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1})\n\t\t\t\treturn span\n\t\t\t},\n\t\t\tsetupLinks: func(links ptrace.SpanLinkSlice) {\n\t\t\t\tlink := links.AppendEmpty()\n\t\t\t\tlink.SetTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})\n\t\t\t\tlink.SetSpanID([8]byte{8, 7, 6, 5, 4, 3, 2, 1})\n\t\t\t\tlink.Attributes().PutStr(conventions.AttributeOpentracingRefType, conventions.AttributeOpentracingRefTypeFollowsFrom)\n\t\t\t},\n\t\t\texpectedRefs:    1,\n\t\t\texpectedRefType: dbmodel.FollowsFrom,\n\t\t\tdescription:     \"Parent reference should be overridden by link with follows-from type\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty span no links\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\treturn scopeSpans.Spans().AppendEmpty()\n\t\t\t},\n\t\t\tsetupLinks: func(_ ptrace.SpanLinkSlice) {\n\t\t\t\t// No links added\n\t\t\t},\n\t\t\texpectedRefs:    0,\n\t\t\texpectedRefType: dbmodel.ChildOf, // Not used in this case\n\t\t\tdescription:     \"Span with no links should have no references\",\n\t\t},\n\t\t{\n\t\t\tname: \"span with client kind\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := scopeSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetKind(ptrace.SpanKindClient) // This triggers spanKindTagFound = true\n\t\t\t\treturn span\n\t\t\t},\n\t\t\tsetupLinks: func(_ ptrace.SpanLinkSlice) {\n\t\t\t\t// No links needed for this test\n\t\t\t},\n\t\t\texpectedRefs:    0,\n\t\t\texpectedRefType: dbmodel.ChildOf,\n\t\t\tdescription:     \"Span with client kind should have span kind tag\",\n\t\t},\n\t\t{\n\t\t\tname: \"span with unspecified kind\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := scopeSpans.Spans().AppendEmpty()\n\t\t\t\tspan.SetKind(ptrace.SpanKindUnspecified) // This triggers spanKindTagFound = false\n\t\t\t\treturn span\n\t\t\t},\n\t\t\tsetupLinks: func(_ ptrace.SpanLinkSlice) {\n\t\t\t\t// No links needed for this test\n\t\t\t},\n\t\t\texpectedRefs:    0,\n\t\t\texpectedRefType: dbmodel.ChildOf,\n\t\t\tdescription:     \"Span with unspecified kind should not have span kind tag\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tspan := tt.setupSpan()\n\t\t\ttt.setupLinks(span.Links())\n\n\t\t\tscopeSpans := ptrace.NewScopeSpans()\n\t\t\tmodelSpan := spanToDbSpan(span, scopeSpans.Scope(), dbmodel.Process{})\n\n\t\t\tassert.Len(t, modelSpan.References, tt.expectedRefs, tt.description)\n\t\t\tif tt.expectedRefs > 0 {\n\t\t\t\tassert.Equal(t, tt.expectedRefType, modelSpan.References[0].RefType)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResourceToDbProcess(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tsetupResource   func() pcommon.Resource\n\t\texpectedService string\n\t\texpectedTags    []dbmodel.KeyValue\n\t\tdescription     string\n\t}{\n\t\t{\n\t\t\tname: \"resource with service name and attributes\",\n\t\t\tsetupResource: func() pcommon.Resource {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tresource := traces.ResourceSpans().AppendEmpty().Resource()\n\t\t\t\tresource.Attributes().PutStr(conventions.ServiceNameKey, \"service\")\n\t\t\t\tresource.Attributes().PutStr(\"foo\", \"bar\")\n\t\t\t\treturn resource\n\t\t\t},\n\t\t\texpectedService: \"service\",\n\t\t\texpectedTags: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"foo\",\n\t\t\t\t\tValue: \"bar\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: \"Resource with service name and additional attributes\",\n\t\t},\n\t\t{\n\t\t\tname: \"resource with only service name\",\n\t\t\tsetupResource: func() pcommon.Resource {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tresource := traces.ResourceSpans().AppendEmpty().Resource()\n\t\t\t\tresource.Attributes().PutStr(conventions.ServiceNameKey, \"service\")\n\t\t\t\treturn resource\n\t\t\t},\n\t\t\texpectedService: \"service\",\n\t\t\texpectedTags:    []dbmodel.KeyValue{},\n\t\t\tdescription:     \"Resource with only service name should have empty tags\",\n\t\t},\n\t\t{\n\t\t\tname: \"resource with no attributes\",\n\t\t\tsetupResource: func() pcommon.Resource {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\treturn traces.ResourceSpans().AppendEmpty().Resource()\n\t\t\t},\n\t\t\texpectedService: noServiceName,\n\t\t\texpectedTags:    nil,\n\t\t\tdescription:     \"Resource with no attributes should use default service name\",\n\t\t},\n\t\t{\n\t\t\tname: \"resource with empty service name\",\n\t\t\tsetupResource: func() pcommon.Resource {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tresource := traces.ResourceSpans().AppendEmpty().Resource()\n\t\t\t\tresource.Attributes().PutStr(conventions.ServiceNameKey, \"\") // Explicitly empty string\n\t\t\t\tresource.Attributes().PutStr(\"foo\", \"bar\")\n\t\t\t\treturn resource\n\t\t\t},\n\t\t\texpectedService: \"\",\n\t\t\texpectedTags: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"foo\",\n\t\t\t\t\tValue: \"bar\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t},\n\t\t\t},\n\t\t\tdescription: \"Resource with empty service name should use empty string\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresource := tt.setupResource()\n\t\t\tprocess := resourceToDbProcess(resource)\n\n\t\t\tassert.Equal(t, tt.expectedService, process.ServiceName, tt.description)\n\t\t\tassert.Equal(t, tt.expectedTags, process.Tags)\n\t\t})\n\t}\n}\n\nfunc TestAttributeConversion(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tsetupAttr func() pcommon.Map\n\t\texpected  []dbmodel.KeyValue\n\t}{\n\t\t{\n\t\t\tname: \"mixed attribute types\",\n\t\t\tsetupAttr: func() pcommon.Map {\n\t\t\t\tattributes := pcommon.NewMap()\n\t\t\t\tattributes.PutBool(\"bool-val\", true)\n\t\t\t\tattributes.PutInt(\"int-val\", 123)\n\t\t\t\tattributes.PutStr(\"string-val\", \"abc\")\n\t\t\t\tattributes.PutDouble(\"double-val\", 1.23)\n\t\t\t\tattributes.PutEmptyBytes(\"bytes-val\").FromRaw([]byte{1, 2, 3, 4})\n\t\t\t\tattributes.PutStr(conventions.ServiceNameKey, \"service-name\")\n\t\t\t\treturn attributes\n\t\t\t},\n\t\t\texpected: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"bool-val\",\n\t\t\t\t\tType:  dbmodel.BoolType,\n\t\t\t\t\tValue: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:   \"int-val\",\n\t\t\t\t\tType:  dbmodel.Int64Type,\n\t\t\t\t\tValue: int64(123),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:   \"string-val\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: \"abc\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:   \"double-val\",\n\t\t\t\t\tType:  dbmodel.Float64Type,\n\t\t\t\t\tValue: 1.23,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:   \"bytes-val\",\n\t\t\t\t\tType:  dbmodel.BinaryType,\n\t\t\t\t\tValue: \"01020304\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:   conventions.ServiceNameKey,\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: \"service-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"empty attributes\",\n\t\t\tsetupAttr: pcommon.NewMap,\n\t\t\texpected:  []dbmodel.KeyValue{},\n\t\t},\n\t\t{\n\t\t\tname: \"map type attributes\",\n\t\t\tsetupAttr: func() pcommon.Map {\n\t\t\t\tattributes := pcommon.NewMap()\n\t\t\t\tattributes.PutEmptyMap(\"empty-map\")\n\t\t\t\treturn attributes\n\t\t\t},\n\t\t\texpected: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"empty-map\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: \"{}\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"slice type attributes\",\n\t\t\tsetupAttr: func() pcommon.Map {\n\t\t\t\tattributes := pcommon.NewMap()\n\t\t\t\tslice := attributes.PutEmptySlice(\"blockers\")\n\t\t\t\tslice.AppendEmpty().SetStr(\"2804-5\")\n\t\t\t\tslice.AppendEmpty().SetStr(\"1234-6\")\n\t\t\t\treturn attributes\n\t\t\t},\n\t\t\texpected: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"blockers\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t\tValue: `[\"2804-5\",\"1234-6\"]`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tattrs := tt.setupAttr()\n\t\t\tresult := appendTagsFromAttributes(make([]dbmodel.KeyValue, 0, len(tt.expected)), attrs)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestStatusMessageHandling(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tstatusMsg   string\n\t\texpectFound bool\n\t\texpected    dbmodel.KeyValue\n\t}{\n\t\t{\n\t\t\tname:        \"empty status message\",\n\t\t\tstatusMsg:   \"\",\n\t\t\texpectFound: false,\n\t\t\texpected:    dbmodel.KeyValue{},\n\t\t},\n\t\t{\n\t\t\tname:        \"non-empty status message\",\n\t\t\tstatusMsg:   \"test-error\",\n\t\t\texpectFound: true,\n\t\t\texpected: dbmodel.KeyValue{\n\t\t\t\tKey:   conventions.OtelStatusDescription,\n\t\t\t\tValue: \"test-error\",\n\t\t\t\tType:  dbmodel.StringType,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, ok := getTagFromStatusMsg(tt.statusMsg)\n\t\t\tassert.Equal(t, tt.expectFound, ok)\n\t\t\tif tt.expectFound {\n\t\t\t\tassert.Equal(t, tt.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRefTypeFromLink(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tsetupLink func() ptrace.SpanLink\n\t\texpected  dbmodel.ReferenceType\n\t}{\n\t\t{\n\t\t\tname: \"link with child_of reference type\",\n\t\t\tsetupLink: func() ptrace.SpanLink {\n\t\t\t\tlink := ptrace.NewSpanLink()\n\t\t\t\tlink.Attributes().PutStr(conventions.AttributeOpentracingRefType, conventions.AttributeOpentracingRefTypeChildOf)\n\t\t\t\treturn link\n\t\t\t},\n\t\t\texpected: dbmodel.ChildOf,\n\t\t},\n\t\t{\n\t\t\tname: \"link with follows_from reference type\",\n\t\t\tsetupLink: func() ptrace.SpanLink {\n\t\t\t\tlink := ptrace.NewSpanLink()\n\t\t\t\tlink.Attributes().PutStr(conventions.AttributeOpentracingRefType, conventions.AttributeOpentracingRefTypeFollowsFrom)\n\t\t\t\treturn link\n\t\t\t},\n\t\t\texpected: dbmodel.FollowsFrom,\n\t\t},\n\t\t{\n\t\t\tname:      \"link without reference type defaults to follows_from\",\n\t\t\tsetupLink: ptrace.NewSpanLink,\n\n\t\t\texpected: dbmodel.FollowsFrom,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tlink := tt.setupLink()\n\t\t\tresult := refTypeFromLink(link)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestTraceStateHandling(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\ttraceState  string\n\t\texpectFound bool\n\t\texpected    []dbmodel.KeyValue\n\t}{\n\t\t{\n\t\t\tname:        \"empty trace state\",\n\t\t\ttraceState:  \"\",\n\t\t\texpectFound: false,\n\t\t\texpected:    nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"non-empty trace state\",\n\t\t\ttraceState:  \"key1=value1,key2=value2\",\n\t\t\texpectFound: true,\n\t\t\texpected: []dbmodel.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   tagW3CTraceState,\n\t\t\t\t\tValue: \"key1=value1,key2=value2\",\n\t\t\t\t\tType:  dbmodel.StringType,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tkeyValues, exists := getTagsFromTraceState(tt.traceState)\n\t\t\tassert.Equal(t, tt.expectFound, exists)\n\t\t\tassert.Equal(t, tt.expected, keyValues)\n\t\t})\n\t}\n}\n\nfunc TestReferenceTypeConversion(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\trefType  string\n\t\texpected dbmodel.ReferenceType\n\t}{\n\t\t{\n\t\t\tname:     \"child of reference\",\n\t\t\trefType:  conventions.AttributeOpentracingRefTypeChildOf,\n\t\t\texpected: dbmodel.ChildOf,\n\t\t},\n\t\t{\n\t\t\tname:     \"follows from reference\",\n\t\t\trefType:  conventions.AttributeOpentracingRefTypeFollowsFrom,\n\t\t\texpected: dbmodel.FollowsFrom,\n\t\t},\n\t\t{\n\t\t\tname:     \"unknown reference type defaults to follows from\",\n\t\t\trefType:  \"any other string\",\n\t\t\texpected: dbmodel.FollowsFrom,\n\t\t},\n\t\t{\n\t\t\tname:     \"empty reference type defaults to follows from\",\n\t\t\trefType:  \"\",\n\t\t\texpected: dbmodel.FollowsFrom,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := strToDbSpanRefType(tt.refType)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestEdgeCases(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tsetupTraces func() ptrace.Traces\n\t\texpected    any\n\t\ttestFunc    func(ptrace.Traces) any\n\t\tdescription string\n\t}{\n\t\t{\n\t\t\tname: \"empty span attributes\",\n\t\t\tsetupTraces: func() ptrace.Traces {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tspans := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tscopeSpans := spans.ScopeSpans().AppendEmpty()\n\t\t\t\tscopeSpans.Spans().AppendEmpty()\n\t\t\t\treturn traces\n\t\t\t},\n\t\t\texpected: true,\n\t\t\ttestFunc: func(traces ptrace.Traces) any {\n\t\t\t\tspans := traces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)\n\t\t\t\tspanScope := traces.ResourceSpans().At(0).ScopeSpans().At(0).Scope()\n\t\t\t\tmodelSpan := spanToDbSpan(spans, spanScope, dbmodel.Process{})\n\t\t\t\treturn len(modelSpan.Tags) == 0\n\t\t\t},\n\t\t\tdescription: \"Empty span attributes should result in no tags\",\n\t\t},\n\t\t{\n\t\t\tname: \"resource spans with no scope spans\",\n\t\t\tsetupTraces: func() ptrace.Traces {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\ttraces.ResourceSpans().AppendEmpty()\n\t\t\t\treturn traces\n\t\t\t},\n\t\t\texpected: true,\n\t\t\ttestFunc: func(traces ptrace.Traces) any {\n\t\t\t\tresourceSpans := traces.ResourceSpans().At(0)\n\t\t\t\tdbSpans := resourceSpansToDbSpans(resourceSpans)\n\t\t\t\treturn len(dbSpans) == 0\n\t\t\t},\n\t\t\tdescription: \"Resource spans with no scope spans should return empty slice\",\n\t\t},\n\t\t{\n\t\t\tname:        \"traces with no resource spans\",\n\t\t\tsetupTraces: ptrace.NewTraces,\n\t\t\texpected:    true,\n\t\t\ttestFunc: func(traces ptrace.Traces) any {\n\t\t\t\tdbSpans := ToDBModel(traces)\n\t\t\t\treturn dbSpans == nil\n\t\t\t},\n\t\t\tdescription: \"Traces with no resource spans should return nil\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttraces := tt.setupTraces()\n\t\t\tresult := tt.testFunc(traces)\n\t\t\tassert.Equal(t, tt.expected, result, tt.description)\n\t\t})\n\t}\n}\n\nfunc TestDbSpanTagsWithStatusAndTraceState(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tsetupSpan       func() ptrace.Span\n\t\texpectedTagKeys []string\n\t\tdescription     string\n\t}{\n\t\t{\n\t\t\tname: \"span with status message and trace state\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\tspan := ptrace.NewSpan()\n\t\t\t\tspan.Status().SetMessage(\"test-error\")\n\t\t\t\tspan.TraceState().FromRaw(\"key1=value1,key2=value2\")\n\t\t\t\treturn span\n\t\t\t},\n\t\t\texpectedTagKeys: []string{conventions.OtelStatusDescription, tagW3CTraceState},\n\t\t\tdescription:     \"Span with both status message and trace state should have both tags\",\n\t\t},\n\t\t{\n\t\t\tname: \"span with only status message\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\tspan := ptrace.NewSpan()\n\t\t\t\tspan.Status().SetMessage(\"test-error\")\n\t\t\t\treturn span\n\t\t\t},\n\t\t\texpectedTagKeys: []string{conventions.OtelStatusDescription},\n\t\t\tdescription:     \"Span with only status message should have only status tag\",\n\t\t},\n\t\t{\n\t\t\tname: \"span with only trace state\",\n\t\t\tsetupSpan: func() ptrace.Span {\n\t\t\t\tspan := ptrace.NewSpan()\n\t\t\t\tspan.TraceState().FromRaw(\"key1=value1,key2=value2\")\n\t\t\t\treturn span\n\t\t\t},\n\t\t\texpectedTagKeys: []string{tagW3CTraceState},\n\t\t\tdescription:     \"Span with only trace state should have only trace state tag\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tspan := tt.setupSpan()\n\t\t\ttags := getDbSpanTags(span, pcommon.NewInstrumentationScope())\n\n\t\t\tassert.Len(t, tags, len(tt.expectedTagKeys), tt.description)\n\t\t\tfor i, expectedKey := range tt.expectedTagKeys {\n\t\t\t\tassert.Equal(t, expectedKey, tags[i].Key)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestToDbModel_Fixtures(t *testing.T) {\n\ttracesStr, spansStr := loadFixtures(t, 1)\n\tvar span dbmodel.Span\n\terr := json.Unmarshal(spansStr, &span)\n\trequire.NoError(t, err)\n\n\ttd, err := FromDBModel([]dbmodel.Span{span})\n\trequire.NoError(t, err)\n\ttestTraces(t, tracesStr, td)\n\n\tspans := ToDBModel(td)\n\tassert.Len(t, spans, 1)\n\ttestSpans(t, spansStr, spans[0])\n}\n\nfunc BenchmarkInternalTracesToDbSpans(b *testing.B) {\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\tdata, err := os.ReadFile(\"fixtures/otel_traces_01.json\")\n\trequire.NoError(b, err)\n\ttd, err := unmarshaller.UnmarshalTraces(data)\n\trequire.NoError(b, err)\n\n\tfor b.Loop() {\n\t\tbatches := ToDBModel(td)\n\t\tassert.NotEmpty(b, batches)\n\t}\n}\n\nfunc writeActualData(t *testing.T, name string, data []byte) {\n\tvar prettyJson bytes.Buffer\n\terr := json.Indent(&prettyJson, data, \"\", \"  \")\n\trequire.NoError(t, err)\n\tpath := \"fixtures/actual_\" + name + \".json\"\n\terr = os.WriteFile(path, prettyJson.Bytes(), 0o644)\n\trequire.NoError(t, err)\n\tt.Log(\"Saved the actual \" + name + \" to \" + path)\n}\n\n// Loads and returns domain model and JSON model fixtures with given number i.\nfunc loadFixtures(t *testing.T, i int) (tracesData []byte, spansData []byte) {\n\ttracesData = loadTraces(t, i)\n\tspansData = loadSpans(t, i)\n\treturn tracesData, spansData\n}\n\nfunc loadTraces(t *testing.T, i int) []byte {\n\tinTraces := fmt.Sprintf(\"fixtures/otel_traces_%02d.json\", i)\n\ttracesData, err := os.ReadFile(inTraces)\n\trequire.NoError(t, err)\n\treturn tracesData\n}\n\nfunc loadSpans(t *testing.T, i int) []byte {\n\tinSpans := fmt.Sprintf(\"fixtures/es_%02d.json\", i)\n\tspansData, err := os.ReadFile(inSpans)\n\trequire.NoError(t, err)\n\treturn spansData\n}\n\nfunc testTraces(t *testing.T, expectedTraces []byte, actualTraces ptrace.Traces) {\n\tunmarshaller := ptrace.JSONUnmarshaler{}\n\texpectedTd, err := unmarshaller.UnmarshalTraces(expectedTraces)\n\trequire.NoError(t, err)\n\tif !assert.Equal(t, expectedTd, actualTraces) {\n\t\tmarshaller := ptrace.JSONMarshaler{}\n\t\tactualTd, err := marshaller.MarshalTraces(actualTraces)\n\t\trequire.NoError(t, err)\n\t\twriteActualData(t, \"traces\", actualTd)\n\t}\n}\n\nfunc testSpans(t *testing.T, expectedSpan []byte, actualSpan dbmodel.Span) {\n\tbuf := &bytes.Buffer{}\n\tenc := json.NewEncoder(buf)\n\tenc.SetIndent(\"\", \"  \")\n\trequire.NoError(t, enc.Encode(actualSpan))\n\tif !assert.Equal(t, string(expectedSpan), buf.String()) {\n\t\twriteActualData(t, \"spans\", buf.Bytes())\n\t}\n}\n\nfunc TestAttributeToDbTag_DefaultCase(t *testing.T) {\n\tattr := pcommon.NewValueEmpty()\n\n\ttag := attributeToDbTag(\"test-key\", attr)\n\n\tassert.Equal(t, \"test-key\", tag.Key)\n\tassert.Equal(t, dbmodel.StringType, tag.Type)\n\tassert.Nil(t, tag.Value)\n}\n\nfunc TestGetTagFromStatusCode_DefaultCase(t *testing.T) {\n\ttag, shouldInclude := getTagFromStatusCode(ptrace.StatusCodeUnset)\n\n\tassert.False(t, shouldInclude)\n\tassert.Equal(t, dbmodel.KeyValue{}, tag)\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/writer.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n)\n\ntype TraceWriter struct {\n\tspanWriter spanstore.CoreSpanWriter\n}\n\n// NewTraceWriter returns the TraceWriter for use\nfunc NewTraceWriter(p spanstore.SpanWriterParams) *TraceWriter {\n\treturn &TraceWriter{\n\t\tspanWriter: spanstore.NewSpanWriter(p),\n\t}\n}\n\n// WriteTraces convert the traces to ES Span model and write into the database\nfunc (t *TraceWriter) WriteTraces(_ context.Context, td ptrace.Traces) error {\n\tdbSpans := ToDBModel(td)\n\tfor i := range dbSpans {\n\t\tspan := &dbSpans[i]\n\t\tt.spanWriter.WriteSpan(model.EpochMicrosecondsAsTime(span.StartTime), span)\n\t}\n\treturn nil\n}\n\nfunc (t *TraceWriter) Close() error {\n\treturn t.spanWriter.Close()\n}\n"
  },
  {
    "path": "internal/storage/v2/elasticsearch/tracestore/writer_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracestore\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/elasticsearch/spanstore/mocks\"\n)\n\nfunc TestTraceWriter_WriteTraces(t *testing.T) {\n\tcoreWriter := &mocks.CoreSpanWriter{}\n\ttd := ptrace.NewTraces()\n\tresourceSpans := td.ResourceSpans().AppendEmpty()\n\tresourceSpans.Resource().Attributes().PutStr(\"service.name\", \"testing-service\")\n\tspan := resourceSpans.ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.SetName(\"op-1\")\n\tdbSpan := ToDBModel(td)\n\twriter := TraceWriter{spanWriter: coreWriter}\n\tcoreWriter.On(\"WriteSpan\", model.EpochMicrosecondsAsTime(dbSpan[0].StartTime), &dbSpan[0])\n\terr := writer.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n}\n\nfunc TestTraceWriter_Close(t *testing.T) {\n\tcoreWriter := &mocks.CoreSpanWriter{}\n\tcoreWriter.On(\"Close\").Return(nil)\n\twriter := TraceWriter{spanWriter: coreWriter}\n\terr := writer.Close()\n\trequire.NoError(t, err)\n}\n\nfunc Test_NewTraceWriter(t *testing.T) {\n\tparams := spanstore.SpanWriterParams{\n\t\tLogger:         zap.NewNop(),\n\t\tMetricsFactory: metrics.NullFactory,\n\t}\n\twriter := NewTraceWriter(params)\n\tassert.NotNil(t, writer)\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/README.md",
    "content": "# gRPC Remote Storage\n\nJaeger supports a gRPC-based Remote Storage API that enables integration with custom storage backends not natively supported by Jaeger.\n\nA remote storage backend must implement a gRPC server with the following services:\n\n- **[TraceReader](https://github.com/jaegertracing/jaeger-idl/tree/main/proto/storage/v2/trace_storage.proto)**\n  Enables Jaeger to read traces from the storage backend.\n- **[DependencyReader](https://github.com/jaegertracing/jaeger-idl/tree/main/proto/storage/v2/dependency_storage.proto)**\n  Used to load service dependency graphs from storage.\n- **[TraceService](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/collector/trace/v1/trace_service.proto)**\n  Allows trace data to be pushed to the storage. This service can run on a separate port if needed.\n\nAn example configuration for setting up a remote storage backend is available\n[here](../../../../cmd/jaeger/config-remote-storage.yaml).\nNote: In this example, the `TraceService` is configured to run on a different port (0.0.0.0:4316), which is overridden in the config file.\n\nThe integration tests also require a POST HTTP endpoint that can be called to purge the storage backend,\nensuring a clean state before each test run.\n\n## Certifying compliance\n\nTo verify that your remote storage backend works correctly with Jaeger, you can run the integration tests provided by the Jaeger project.\n\n### Step 1: Clone the Jaeger Repository\n\nBegin by cloning the Jaeger repository to your local machine:\n\n```bash\ngit clone https://github.com/jaegertracing/jaeger.git\ncd jaeger\n```\n\n### Step 2: Run the Integration Tests\n\nRun the integration tests for the gRPC storage backend using the following command:\n\n```bash\nSTORAGE=grpc \\\nCUSTOM_STORAGE=true \\\nREMOTE_STORAGE_ENDPOINT=${MY_REMOTE_STORAGE_ENDPOINT} \\\nREMOTE_STORAGE_WRITER_ENDPOINT=${MY_REMOTE_STORAGE_WRITER_ENDPOINT} \\\nPURGER_ENDPOINT=${MY_PURGER_ENDPOINT} \\\nmake jaeger-v2-storage-integration-test\n```\n\nThe diagram below demonstrates the architecture of the gRPC storage integration test.\n\n``` mermaid\nflowchart LR\n\nTest --> |writeSpan| SpanWriter\nTest --> |http:$PURGER_ENDPOINT| Purger\nSpanWriter --> |0.0.0.0:4317| OTLP_Receiver1\nOTLP_Receiver1 --> GRPCStorage\nGRPCStorage --> |grpc:$REMOTE_STORAGE_WRITER_ENDPOINT| TraceService\nTest --> |readSpan| SpanReader\nSpanReader --> |0.0.0.0:16685| QueryExtension\nQueryExtension --> GRPCStorage\nGRPCStorage --> |grpc:$REMOTE_STORAGE_ENDPOINT| TraceReader\nGRPCStorage --> |grpc:$REMOTE_STORAGE_ENDPOINT| DependencyReader\nsubgraph Integration Test Executable\n    Test\n    SpanWriter\n    SpanReader\nend\nsubgraph Jaeger Collector\n    OTLP_Receiver1[OTLP Receiver]\n    QueryExtension[Query Extension]\n    GRPCStorage[gRPC Storage]\nend\nsubgraph Custom Storage Backend\n    TraceService\n    TraceReader\n    DependencyReader\n    Purger[HTTP/Purger]\nend\n```\n"
  },
  {
    "path": "internal/storage/v2/grpc/config.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/exporter/exporterhelper\"\n\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\ntype Config struct {\n\tconfiggrpc.ClientConfig `mapstructure:\",squash\"`\n\t// Writer allows overriding the endpoint for writes, e.g. to an OTLP receiver.\n\t// If not defined the main endpoint is used for reads and writes.\n\tWriter configgrpc.ClientConfig `mapstructure:\"writer\"`\n\n\tTenancy                      tenancy.Options `mapstructure:\"multi_tenancy\"`\n\texporterhelper.TimeoutConfig `mapstructure:\",squash\"`\n}\n\nfunc DefaultConfig() Config {\n\treturn Config{\n\t\tTimeoutConfig: exporterhelper.TimeoutConfig{\n\t\t\tTimeout: time.Duration(5 * time.Second),\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/config_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDefaultConfig(t *testing.T) {\n\tcfg := DefaultConfig()\n\trequire.NotEmpty(t, cfg.Timeout)\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/depreader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n)\n\nvar _ depstore.Reader = (*DependencyReader)(nil)\n\ntype DependencyReader struct {\n\tclient storage.DependencyReaderClient\n}\n\n// NewDependencyReader creates a DependencyReader that communicates with a remote gRPC storage server.\n// The provided gRPC connection is used exclusively for reading dependencies, meaning it is safe\n// to enable instrumentation on the connection.\nfunc NewDependencyReader(conn *grpc.ClientConn) *DependencyReader {\n\treturn &DependencyReader{\n\t\tclient: storage.NewDependencyReaderClient(conn),\n\t}\n}\n\nfunc (dr *DependencyReader) GetDependencies(\n\tctx context.Context,\n\tquery depstore.QueryParameters,\n) ([]model.DependencyLink, error) {\n\tresp, err := dr.client.GetDependencies(ctx, &storage.GetDependenciesRequest{\n\t\tStartTime: query.StartTime,\n\t\tEndTime:   query.EndTime,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get dependencies: %w\", err)\n\t}\n\tdependencies := make([]model.DependencyLink, len(resp.Dependencies))\n\tfor i, dep := range resp.Dependencies {\n\t\tdependencies[i] = model.DependencyLink{\n\t\t\tParent:    dep.Parent,\n\t\t\tChild:     dep.Child,\n\t\t\tCallCount: dep.CallCount,\n\t\t\tSource:    dep.Source,\n\t\t}\n\t}\n\treturn dependencies, nil\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/depreader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n)\n\n// testDependenciesServer implements the storage.DependencyReaderServer interface\n// to simulate responses for testing.\ntype testDependenciesServer struct {\n\tstorage.UnimplementedDependencyReaderServer\n\n\tdependencies []*storage.Dependency\n\terr          error\n}\n\nfunc (t *testDependenciesServer) GetDependencies(\n\tcontext.Context,\n\t*storage.GetDependenciesRequest,\n) (*storage.GetDependenciesResponse, error) {\n\treturn &storage.GetDependenciesResponse{\n\t\tDependencies: t.dependencies,\n\t}, t.err\n}\n\nfunc startTestDependenciesServer(t *testing.T, testServer *testDependenciesServer) *grpc.ClientConn {\n\tlistener, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err)\n\n\tserver := grpc.NewServer()\n\tstorage.RegisterDependencyReaderServer(server, testServer)\n\n\treturn startServer(t, server, listener)\n}\n\nfunc TestDependencyReader_GetDependencies(t *testing.T) {\n\ttests := []struct {\n\t\tname                 string\n\t\ttestServer           *testDependenciesServer\n\t\texpectedDependencies []model.DependencyLink\n\t\texpectedError        string\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\ttestServer: &testDependenciesServer{\n\t\t\t\tdependencies: []*storage.Dependency{\n\t\t\t\t\t{\n\t\t\t\t\t\tParent:    \"service-a\",\n\t\t\t\t\t\tChild:     \"service-b\",\n\t\t\t\t\t\tCallCount: 42,\n\t\t\t\t\t\tSource:    \"source\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tParent:    \"service-c\",\n\t\t\t\t\t\tChild:     \"service-d\",\n\t\t\t\t\t\tCallCount: 24,\n\t\t\t\t\t\tSource:    \"source\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedDependencies: []model.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"service-a\",\n\t\t\t\t\tChild:     \"service-b\",\n\t\t\t\t\tCallCount: 42,\n\t\t\t\t\tSource:    \"source\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tParent:    \"service-c\",\n\t\t\t\t\tChild:     \"service-d\",\n\t\t\t\t\tCallCount: 24,\n\t\t\t\t\tSource:    \"source\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\ttestServer: &testDependenciesServer{\n\t\t\t\tdependencies: []*storage.Dependency{},\n\t\t\t},\n\t\t\texpectedDependencies: []model.DependencyLink{},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\ttestServer: &testDependenciesServer{\n\t\t\t\terr: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to get dependencies\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn := startTestDependenciesServer(t, test.testServer)\n\n\t\t\treader := NewDependencyReader(conn)\n\t\t\tdependencies, err := reader.GetDependencies(context.Background(), depstore.QueryParameters{})\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, test.expectedDependencies, dependencies)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"go.opentelemetry.io/collector/component\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.opentelemetry.io/otel/trace/noop\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger/internal/auth/bearertoken\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\nvar (\n\t_ io.Closer          = (*Factory)(nil)\n\t_ tracestore.Factory = (*Factory)(nil)\n\t_ depstore.Factory   = (*Factory)(nil)\n)\n\ntype Factory struct {\n\ttelset telemetry.Settings\n\tconfig Config\n\t// readerConn is the gRPC connection used for reading data from the remote storage backend.\n\t// It is safe for this connection to have instrumentation enabled without\n\t// the risk of recursively generating traces.\n\treaderConn *grpc.ClientConn\n\t// writerConn is the gRPC connection used for writing data to the remote storage backend.\n\t// This connection should not have instrumentation enabled to avoid recursively generating traces.\n\twriterConn *grpc.ClientConn\n}\n\n// NewFactory initializes a new gRPC (remote) storage backend.\nfunc NewFactory(\n\tctx context.Context,\n\tcfg Config,\n\ttelset telemetry.Settings,\n) (*Factory, error) {\n\tf := &Factory{\n\t\ttelset: telset,\n\t\tconfig: cfg,\n\t}\n\n\tvar writerConfig configgrpc.ClientConfig\n\tif cfg.Writer.Endpoint != \"\" {\n\t\twriterConfig = cfg.Writer\n\t} else {\n\t\twriterConfig = cfg.ClientConfig\n\t}\n\n\treaderTelset := getTelset(f.telset, f.telset.TracerProvider)\n\twriterTelset := getTelset(f.telset, noop.NewTracerProvider())\n\tnewClientFn := func(telset component.TelemetrySettings, gcs *configgrpc.ClientConfig, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {\n\t\tclientOpts := make([]configgrpc.ToClientConnOption, 0)\n\t\tfor _, opt := range opts {\n\t\t\tclientOpts = append(clientOpts, configgrpc.WithGrpcDialOption(opt))\n\t\t}\n\t\treturn gcs.ToClientConn(ctx, f.telset.Host.GetExtensions(), telset, clientOpts...)\n\t}\n\n\tif err := f.initializeConnections(readerTelset, writerTelset, &cfg.ClientConfig, &writerConfig, newClientFn); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn f, nil\n}\n\nfunc (f *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\treturn NewTraceReader(f.readerConn), nil\n}\n\nfunc (f *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\treturn NewTraceWriter(f.writerConn), nil\n}\n\nfunc (f *Factory) CreateDependencyReader() (depstore.Reader, error) {\n\treturn NewDependencyReader(f.readerConn), nil\n}\n\nfunc (f *Factory) Close() error {\n\tvar errs []error\n\tif f.readerConn != nil {\n\t\terrs = append(errs, f.readerConn.Close())\n\t}\n\tif f.writerConn != nil {\n\t\terrs = append(errs, f.writerConn.Close())\n\t}\n\treturn errors.Join(errs...)\n}\n\nfunc getTelset(\n\ttelset telemetry.Settings,\n\ttracerProvider trace.TracerProvider,\n) component.TelemetrySettings {\n\treturn component.TelemetrySettings{\n\t\tLogger:         telset.Logger,\n\t\tTracerProvider: tracerProvider,\n\t\tMeterProvider:  telset.MeterProvider,\n\t}\n}\n\ntype newClientFn func(telset component.TelemetrySettings, gcs *configgrpc.ClientConfig, opts ...grpc.DialOption) (*grpc.ClientConn, error)\n\nfunc (f *Factory) initializeConnections(\n\treaderTelset, writerTelset component.TelemetrySettings,\n\treaderConfig, writerConfig *configgrpc.ClientConfig,\n\tnewClient newClientFn,\n) error {\n\tif f.config.Auth.HasValue() {\n\t\treturn errors.New(\"authenticator is not supported\")\n\t}\n\n\tunaryInterceptors := []grpc.UnaryClientInterceptor{bearertoken.NewUnaryClientInterceptor()}\n\tstreamInterceptors := []grpc.StreamClientInterceptor{bearertoken.NewStreamClientInterceptor()}\n\n\tif tenancyMgr := tenancy.NewManager(&f.config.Tenancy); tenancyMgr.Enabled {\n\t\tunaryInterceptors = append(unaryInterceptors, tenancy.NewClientUnaryInterceptor(tenancyMgr))\n\t\tstreamInterceptors = append(streamInterceptors, tenancy.NewClientStreamInterceptor(tenancyMgr))\n\t}\n\n\tbaseOpts := []grpc.DialOption{\n\t\tgrpc.WithChainUnaryInterceptor(unaryInterceptors...),\n\t\tgrpc.WithChainStreamInterceptor(streamInterceptors...),\n\t}\n\n\tcreateConn := func(telset component.TelemetrySettings, gcs *configgrpc.ClientConfig) (*grpc.ClientConn, error) {\n\t\topts := append(baseOpts, grpc.WithStatsHandler(\n\t\t\totelgrpc.NewClientHandler(\n\t\t\t\totelgrpc.WithTracerProvider(telset.TracerProvider),\n\t\t\t\totelgrpc.WithMeterProvider(telset.MeterProvider),\n\t\t\t),\n\t\t))\n\t\treturn newClient(telset, gcs, opts...)\n\t}\n\n\treaderConn, err := createConn(readerTelset, readerConfig)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating reader client connection: %w\", err)\n\t}\n\twriterConn, err := createConn(writerTelset, writerConfig)\n\tif err != nil {\n\t\t_ = readerConn.Close()\n\t\treturn fmt.Errorf(\"error creating writer client connection: %w\", err)\n\t}\n\n\tf.readerConn, f.writerConn = readerConn, writerConn\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/component\"\n\t\"go.opentelemetry.io/collector/config/configauth\"\n\t\"go.opentelemetry.io/collector/config/configgrpc\"\n\t\"go.opentelemetry.io/collector/config/configoptional\"\n\t\"go.opentelemetry.io/collector/exporter/exporterhelper\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\nfunc TestNewFactory_NonEmptyAuthenticator(t *testing.T) {\n\tcfg := &Config{\n\t\tClientConfig: configgrpc.ClientConfig{\n\t\t\tAuth: configoptional.Some(configauth.Config{}),\n\t\t},\n\t}\n\t_, err := NewFactory(context.Background(), *cfg, telemetry.NoopSettings())\n\trequire.ErrorContains(t, err, \"authenticator is not supported\")\n}\n\nfunc TestNewFactory(t *testing.T) {\n\tlis, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err, \"failed to listen\")\n\tt.Cleanup(func() { require.NoError(t, lis.Close()) })\n\n\tcfg := Config{\n\t\tClientConfig: configgrpc.ClientConfig{\n\t\t\tEndpoint: lis.Addr().String(),\n\t\t},\n\t\tTimeoutConfig: exporterhelper.TimeoutConfig{\n\t\t\tTimeout: 1 * time.Second,\n\t\t},\n\t\tTenancy: tenancy.Options{\n\t\t\tEnabled: true,\n\t\t},\n\t}\n\ttelset := telemetry.NoopSettings()\n\tf, err := NewFactory(context.Background(), cfg, telset)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { require.NoError(t, f.Close()) })\n\trequire.Equal(t, lis.Addr().String(), f.readerConn.Target())\n\trequire.Equal(t, lis.Addr().String(), f.writerConn.Target())\n}\n\nfunc TestNewFactory_WriteEndpointOverride(t *testing.T) {\n\treadListener, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err, \"failed to listen\")\n\tt.Cleanup(func() { require.NoError(t, readListener.Close()) })\n\n\twriteListener, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err, \"failed to listen\")\n\tt.Cleanup(func() { require.NoError(t, writeListener.Close()) })\n\n\tcfg := Config{\n\t\tClientConfig: configgrpc.ClientConfig{\n\t\t\tEndpoint: readListener.Addr().String(),\n\t\t},\n\t\tWriter: configgrpc.ClientConfig{\n\t\t\tEndpoint: writeListener.Addr().String(),\n\t\t},\n\t\tTimeoutConfig: exporterhelper.TimeoutConfig{\n\t\t\tTimeout: 1 * time.Second,\n\t\t},\n\t\tTenancy: tenancy.Options{\n\t\t\tEnabled: true,\n\t\t},\n\t}\n\ttelset := telemetry.NoopSettings()\n\tf, err := NewFactory(context.Background(), cfg, telset)\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { require.NoError(t, f.Close()) })\n\trequire.Equal(t, readListener.Addr().String(), f.readerConn.Target())\n\trequire.Equal(t, writeListener.Addr().String(), f.writerConn.Target())\n}\n\nfunc TestFactory(t *testing.T) {\n\tlis, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err, \"failed to listen\")\n\n\ts := grpc.NewServer()\n\n\tconn := startServer(t, s, lis)\n\tf := &Factory{\n\t\treaderConn: conn,\n\t}\n\n\tt.Run(\"CreateTraceReader\", func(t *testing.T) {\n\t\ttr, err := f.CreateTraceReader()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tr)\n\t})\n\n\tt.Run(\"CreateTraceWriter\", func(t *testing.T) {\n\t\ttr, err := f.CreateTraceWriter()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tr)\n\t})\n\n\tt.Run(\"CreateDependencyReader\", func(t *testing.T) {\n\t\ttr, err := f.CreateDependencyReader()\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, tr)\n\t})\n}\n\nfunc TestInitializeConnections_ClientError(t *testing.T) {\n\tf, err := NewFactory(\n\t\tcontext.Background(),\n\t\tConfig{\n\t\t\tClientConfig: configgrpc.ClientConfig{\n\t\t\t\tEndpoint: \":0\",\n\t\t\t},\n\t\t}, telemetry.NoopSettings())\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { require.NoError(t, f.Close()) })\n\tnewClientFn := func(_ component.TelemetrySettings, _ *configgrpc.ClientConfig, _ ...grpc.DialOption) (conn *grpc.ClientConn, err error) {\n\t\treturn nil, assert.AnError\n\t}\n\tnoopTelset := telemetry.NoopSettings().ToOtelComponent()\n\terr = f.initializeConnections(\n\t\tnoopTelset,\n\t\tnoopTelset,\n\t\t&configgrpc.ClientConfig{},\n\t\t&configgrpc.ClientConfig{},\n\t\tnewClientFn,\n\t)\n\tassert.ErrorContains(t, err, \"error creating reader client connection\")\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/handler.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/health\"\n\t\"google.golang.org/grpc/health/grpc_health_v1\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar (\n\t_ storage.TraceReaderServer      = (*Handler)(nil)\n\t_ storage.DependencyReaderServer = (*Handler)(nil)\n\t_ ptraceotlp.GRPCServer          = (*Handler)(nil)\n)\n\ntype Handler struct {\n\tstorage.UnimplementedTraceReaderServer\n\tstorage.UnimplementedDependencyReaderServer\n\tptraceotlp.UnimplementedGRPCServer\n\n\ttraceReader tracestore.Reader\n\ttraceWriter tracestore.Writer\n\tdepReader   depstore.Reader\n}\n\nfunc NewHandler(\n\ttraceReader tracestore.Reader,\n\ttraceWriter tracestore.Writer,\n\tdepReader depstore.Reader,\n) *Handler {\n\treturn &Handler{\n\t\ttraceReader: traceReader,\n\t\ttraceWriter: traceWriter,\n\t\tdepReader:   depReader,\n\t}\n}\n\nfunc (h *Handler) GetTraces(\n\treq *storage.GetTracesRequest,\n\tsrv storage.TraceReader_GetTracesServer,\n) error {\n\ttraceIDs := make([]tracestore.GetTraceParams, len(req.Query))\n\tfor i, query := range req.Query {\n\t\tvar sizedTraceID [16]byte\n\t\tcopy(sizedTraceID[:], query.TraceId)\n\n\t\ttraceIDs[i] = tracestore.GetTraceParams{\n\t\t\tTraceID: pcommon.TraceID(sizedTraceID),\n\t\t\tStart:   query.StartTime,\n\t\t\tEnd:     query.EndTime,\n\t\t}\n\t}\n\tfor traces, err := range h.traceReader.GetTraces(srv.Context(), traceIDs...) {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, trace := range traces {\n\t\t\ttd := jptrace.TracesData(trace)\n\t\t\terr = srv.Send(&td)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *Handler) GetServices(\n\tctx context.Context,\n\t_ *storage.GetServicesRequest,\n) (*storage.GetServicesResponse, error) {\n\tservices, err := h.traceReader.GetServices(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &storage.GetServicesResponse{\n\t\tServices: services,\n\t}, nil\n}\n\nfunc (h *Handler) GetOperations(\n\tctx context.Context,\n\treq *storage.GetOperationsRequest,\n) (*storage.GetOperationsResponse, error) {\n\toperations, err := h.traceReader.GetOperations(ctx, tracestore.OperationQueryParams{\n\t\tServiceName: req.Service,\n\t\tSpanKind:    req.SpanKind,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgrpcOperations := make([]*storage.Operation, len(operations))\n\tfor i, operation := range operations {\n\t\tgrpcOperations[i] = &storage.Operation{\n\t\t\tName:     operation.Name,\n\t\t\tSpanKind: operation.SpanKind,\n\t\t}\n\t}\n\treturn &storage.GetOperationsResponse{\n\t\tOperations: grpcOperations,\n\t}, nil\n}\n\nfunc (h *Handler) FindTraces(\n\treq *storage.FindTracesRequest,\n\tsrv storage.TraceReader_FindTracesServer,\n) error {\n\tfor traces, err := range h.traceReader.FindTraces(srv.Context(), toTraceQueryParams(req.Query)) {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, trace := range traces {\n\t\t\ttd := jptrace.TracesData(trace)\n\t\t\terr = srv.Send(&td)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *Handler) FindTraceIDs(\n\tctx context.Context,\n\treq *storage.FindTracesRequest,\n) (*storage.FindTraceIDsResponse, error) {\n\tfoundTraceIDs := []*storage.FoundTraceID{}\n\tfor traceIDs, err := range h.traceReader.FindTraceIDs(ctx, toTraceQueryParams(req.Query)) {\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, traceID := range traceIDs {\n\t\t\tfoundTraceIDs = append(foundTraceIDs, &storage.FoundTraceID{\n\t\t\t\tTraceId: traceID.TraceID[:],\n\t\t\t\tStart:   traceID.Start,\n\t\t\t\tEnd:     traceID.End,\n\t\t\t})\n\t\t}\n\t}\n\treturn &storage.FindTraceIDsResponse{\n\t\tTraceIds: foundTraceIDs,\n\t}, nil\n}\n\nfunc (h *Handler) Export(ctx context.Context, request ptraceotlp.ExportRequest) (\n\tptraceotlp.ExportResponse,\n\terror,\n) {\n\terr := h.traceWriter.WriteTraces(ctx, request.Traces())\n\tif err != nil {\n\t\treturn ptraceotlp.NewExportResponse(), err\n\t}\n\treturn ptraceotlp.NewExportResponse(), nil\n}\n\nfunc (h *Handler) GetDependencies(\n\tctx context.Context,\n\treq *storage.GetDependenciesRequest,\n) (*storage.GetDependenciesResponse, error) {\n\tdependencies, err := h.depReader.GetDependencies(ctx, depstore.QueryParameters{\n\t\tStartTime: req.StartTime,\n\t\tEndTime:   req.EndTime,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgrpcDependencies := make([]*storage.Dependency, len(dependencies))\n\tfor i, dependency := range dependencies {\n\t\tgrpcDependencies[i] = &storage.Dependency{\n\t\t\tParent:    dependency.Parent,\n\t\t\tChild:     dependency.Child,\n\t\t\tCallCount: dependency.CallCount,\n\t\t\tSource:    dependency.Source,\n\t\t}\n\t}\n\treturn &storage.GetDependenciesResponse{\n\t\tDependencies: grpcDependencies,\n\t}, nil\n}\n\nfunc (h *Handler) Register(ss *grpc.Server, hs *health.Server) {\n\tstorage.RegisterTraceReaderServer(ss, h)\n\tstorage.RegisterDependencyReaderServer(ss, h)\n\tptraceotlp.RegisterGRPCServer(ss, h)\n\n\ths.SetServingStatus(\"jaeger.storage.v2.TraceReader\", grpc_health_v1.HealthCheckResponse_SERVING)\n\ths.SetServingStatus(\"jaeger.storage.v2.DependencyReader\", grpc_health_v1.HealthCheckResponse_SERVING)\n\ths.SetServingStatus(\"jaeger.storage.v2.TraceWriter\", grpc_health_v1.HealthCheckResponse_SERVING)\n}\n\nfunc toTraceQueryParams(t *storage.TraceQueryParameters) tracestore.TraceQueryParams {\n\treturn tracestore.TraceQueryParams{\n\t\tServiceName:   t.ServiceName,\n\t\tOperationName: t.OperationName,\n\t\tAttributes:    convertKeyValueListToMap(t.Attributes),\n\t\tStartTimeMin:  t.StartTimeMin,\n\t\tStartTimeMax:  t.StartTimeMax,\n\t\tDurationMin:   t.DurationMin,\n\t\tDurationMax:   t.DurationMax,\n\t\tSearchDepth:   int(t.SearchDepth),\n\t}\n}\n\nfunc convertKeyValueListToMap(kvList []*storage.KeyValue) pcommon.Map {\n\tm := pcommon.NewMap()\n\tfor _, kv := range kvList {\n\t\tif kv == nil || kv.Value == nil {\n\t\t\tcontinue\n\t\t}\n\t\tsetValueToMap(m, kv.Key, kv.Value)\n\t}\n\treturn m\n}\n\nfunc setValueToMap(m pcommon.Map, key string, av *storage.AnyValue) {\n\tswitch v := av.Value.(type) {\n\tcase *storage.AnyValue_StringValue:\n\t\tm.PutStr(key, v.StringValue)\n\tcase *storage.AnyValue_BoolValue:\n\t\tm.PutBool(key, v.BoolValue)\n\tcase *storage.AnyValue_IntValue:\n\t\tm.PutInt(key, v.IntValue)\n\tcase *storage.AnyValue_DoubleValue:\n\t\tm.PutDouble(key, v.DoubleValue)\n\tcase *storage.AnyValue_BytesValue:\n\t\tm.PutEmptyBytes(key).FromRaw(v.BytesValue)\n\tcase *storage.AnyValue_ArrayValue:\n\t\tsliceVal := m.PutEmptySlice(key)\n\t\tfor _, elem := range v.ArrayValue.Values {\n\t\t\tif elem == nil {\n\t\t\t\tsliceVal.AppendEmpty()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsetValueToSlice(sliceVal, elem)\n\t\t}\n\tcase *storage.AnyValue_KvlistValue:\n\t\tmapVal := m.PutEmptyMap(key)\n\t\tfor _, kv := range v.KvlistValue.Values {\n\t\t\tif kv == nil || kv.Value == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsetValueToMap(mapVal, kv.Key, kv.Value)\n\t\t}\n\tdefault:\n\t\treturn // unreachable\n\t}\n}\n\nfunc setValueToSlice(slice pcommon.Slice, av *storage.AnyValue) {\n\tswitch v := av.Value.(type) {\n\tcase *storage.AnyValue_StringValue:\n\t\tslice.AppendEmpty().SetStr(v.StringValue)\n\tcase *storage.AnyValue_BoolValue:\n\t\tslice.AppendEmpty().SetBool(v.BoolValue)\n\tcase *storage.AnyValue_IntValue:\n\t\tslice.AppendEmpty().SetInt(v.IntValue)\n\tcase *storage.AnyValue_DoubleValue:\n\t\tslice.AppendEmpty().SetDouble(v.DoubleValue)\n\tcase *storage.AnyValue_BytesValue:\n\t\tslice.AppendEmpty().SetEmptyBytes().FromRaw(v.BytesValue)\n\tcase *storage.AnyValue_ArrayValue:\n\t\tnewSlice := slice.AppendEmpty().SetEmptySlice()\n\t\tfor _, subElem := range v.ArrayValue.Values {\n\t\t\tif subElem == nil {\n\t\t\t\tnewSlice.AppendEmpty()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsetValueToSlice(newSlice, subElem)\n\t\t}\n\tcase *storage.AnyValue_KvlistValue:\n\t\tnewMap := slice.AppendEmpty().SetEmptyMap()\n\t\tfor _, kv := range v.KvlistValue.Values {\n\t\t\tif kv == nil || kv.Value == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsetValueToMap(newMap, kv.Key, kv.Value)\n\t\t}\n\tdefault:\n\t\treturn // unreachable\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/handler_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"iter\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\tdepstoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\ttracestoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/mocks\"\n)\n\ntype testStream struct {\n\tgrpc.ServerStream\n\tsent    []*jptrace.TracesData\n\tsendErr error\n}\n\nfunc (*testStream) Context() context.Context {\n\treturn context.Background()\n}\n\nfunc (f *testStream) Send(td *jptrace.TracesData) error {\n\tif f.sendErr != nil {\n\t\treturn f.sendErr\n\t}\n\tf.sent = append(f.sent, td)\n\treturn nil\n}\n\nfunc TestHandler_GetTraces(t *testing.T) {\n\tstart := time.Now()\n\tend := start.Add(time.Minute)\n\tquery := []tracestore.GetTraceParams{\n\t\t{\n\t\t\tTraceID: pcommon.TraceID([16]byte{1}),\n\t\t\tStart:   start,\n\t\t\tEnd:     end,\n\t\t},\n\t}\n\ttrace := makeTestTrace()\n\ttd := jptrace.TracesData(trace)\n\n\ttests := []struct {\n\t\tname         string\n\t\ttraces       [][]ptrace.Traces\n\t\texpectedSent []*jptrace.TracesData\n\t\tsendErr      error\n\t\tgetTraceErr  error\n\t\texpectedErr  error\n\t}{\n\t\t{\n\t\t\tname:   \"single trace\",\n\t\t\ttraces: [][]ptrace.Traces{{trace}},\n\t\t\texpectedSent: []*jptrace.TracesData{\n\t\t\t\t&td,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"multiple traces\",\n\t\t\ttraces:       [][]ptrace.Traces{{trace, trace}},\n\t\t\texpectedSent: []*jptrace.TracesData{&td, &td},\n\t\t},\n\t\t{\n\t\t\tname:         \"multiple chunks\",\n\t\t\ttraces:       [][]ptrace.Traces{{trace, trace}, {trace, trace}},\n\t\t\texpectedSent: []*jptrace.TracesData{&td, &td, &td, &td},\n\t\t},\n\t\t{\n\t\t\tname:        \"storage error\",\n\t\t\tgetTraceErr: assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname:        \"send error\",\n\t\t\ttraces:      [][]ptrace.Traces{{trace, trace}},\n\t\t\tsendErr:     assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\twriter := new(tracestoremocks.Writer)\n\t\t\tdepReader := new(depstoremocks.Reader)\n\t\t\treader.On(\"GetTraces\", mock.Anything, query).\n\t\t\t\tReturn(iter.Seq2[[]ptrace.Traces, error](func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\t\tif test.getTraceErr != nil {\n\t\t\t\t\t\tyield(nil, test.getTraceErr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor _, traces := range test.traces {\n\t\t\t\t\t\tif !yield(traces, nil) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})).Once()\n\n\t\t\tserver := NewHandler(reader, writer, depReader)\n\t\t\tstream := &testStream{\n\t\t\t\tsendErr: test.sendErr,\n\t\t\t}\n\t\t\terr := server.GetTraces(&storage.GetTracesRequest{\n\t\t\t\tQuery: []*storage.GetTraceParams{\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceId:   []byte{1},\n\t\t\t\t\t\tStartTime: start,\n\t\t\t\t\t\tEndTime:   end,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, stream)\n\t\t\tif test.expectedErr != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedSent, stream.sent)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandler_GetServices(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tservices         []string\n\t\terr              error\n\t\texpectedServices []string\n\t\texpectedErr      error\n\t}{\n\t\t{\n\t\t\tname:             \"success\",\n\t\t\tservices:         []string{\"service1\", \"service2\"},\n\t\t\texpectedServices: []string{\"service1\", \"service2\"},\n\t\t},\n\t\t{\n\t\t\tname:             \"empty\",\n\t\t\tservices:         []string{},\n\t\t\texpectedServices: []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"error\",\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\twriter := new(tracestoremocks.Writer)\n\t\t\tdepReader := new(depstoremocks.Reader)\n\t\t\treader.On(\"GetServices\", mock.Anything).\n\t\t\t\tReturn(test.services, test.err).Once()\n\n\t\t\tserver := NewHandler(reader, writer, depReader)\n\t\t\tresp, err := server.GetServices(context.Background(), &storage.GetServicesRequest{})\n\t\t\tif test.expectedErr == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedServices, resp.Services)\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandler_GetOperations(t *testing.T) {\n\tparams := tracestore.OperationQueryParams{\n\t\tServiceName: \"service\",\n\t\tSpanKind:    \"kind\",\n\t}\n\treq := &storage.GetOperationsRequest{\n\t\tService:  \"service\",\n\t\tSpanKind: \"kind\",\n\t}\n\ttests := []struct {\n\t\tname               string\n\t\toperations         []tracestore.Operation\n\t\terr                error\n\t\texpectedOperations []*storage.Operation\n\t\texpectedErr        error\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\toperations: []tracestore.Operation{\n\t\t\t\t{Name: \"operation1\", SpanKind: \"kind\"},\n\t\t\t\t{Name: \"operation2\", SpanKind: \"kind\"},\n\t\t\t},\n\t\t\texpectedOperations: []*storage.Operation{\n\t\t\t\t{Name: \"operation1\", SpanKind: \"kind\"},\n\t\t\t\t{Name: \"operation2\", SpanKind: \"kind\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"empty\",\n\t\t\toperations:         []tracestore.Operation{},\n\t\t\texpectedOperations: []*storage.Operation{},\n\t\t},\n\t\t{\n\t\t\tname:        \"error\",\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\twriter := new(tracestoremocks.Writer)\n\t\t\tdepReader := new(depstoremocks.Reader)\n\t\t\treader.On(\"GetOperations\", mock.Anything, params).\n\t\t\t\tReturn(test.operations, test.err).Once()\n\n\t\t\tserver := NewHandler(reader, writer, depReader)\n\t\t\tresp, err := server.GetOperations(context.Background(), req)\n\t\t\tif test.expectedErr == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedOperations, resp.Operations)\n\t\t\t} else {\n\t\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandler_FindTraces(t *testing.T) {\n\tquery := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service\",\n\t\tOperationName: \"operation\",\n\t\tAttributes:    pcommon.NewMap(),\n\t}\n\ttrace := makeTestTrace()\n\ttd := jptrace.TracesData(trace)\n\n\ttests := []struct {\n\t\tname         string\n\t\ttraces       [][]ptrace.Traces\n\t\texpectedSent []*jptrace.TracesData\n\t\tsendErr      error\n\t\tgetTraceErr  error\n\t\texpectedErr  error\n\t}{\n\t\t{\n\t\t\tname:   \"single trace\",\n\t\t\ttraces: [][]ptrace.Traces{{trace}},\n\t\t\texpectedSent: []*jptrace.TracesData{\n\t\t\t\t&td,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"multiple traces\",\n\t\t\ttraces:       [][]ptrace.Traces{{trace, trace}},\n\t\t\texpectedSent: []*jptrace.TracesData{&td, &td},\n\t\t},\n\t\t{\n\t\t\tname:         \"multiple chunks\",\n\t\t\ttraces:       [][]ptrace.Traces{{trace, trace}, {trace, trace}},\n\t\t\texpectedSent: []*jptrace.TracesData{&td, &td, &td, &td},\n\t\t},\n\t\t{\n\t\t\tname:        \"storage error\",\n\t\t\tgetTraceErr: assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname:        \"send error\",\n\t\t\ttraces:      [][]ptrace.Traces{{trace, trace}},\n\t\t\tsendErr:     assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\twriter := new(tracestoremocks.Writer)\n\t\t\tdepReader := new(depstoremocks.Reader)\n\t\t\treader.On(\"FindTraces\", mock.Anything, query).\n\t\t\t\tReturn(iter.Seq2[[]ptrace.Traces, error](func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\t\tif test.getTraceErr != nil {\n\t\t\t\t\t\tyield(nil, test.getTraceErr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor _, traces := range test.traces {\n\t\t\t\t\t\tif !yield(traces, nil) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})).Once()\n\n\t\t\tserver := NewHandler(reader, writer, depReader)\n\t\t\tstream := &testStream{\n\t\t\t\tsendErr: test.sendErr,\n\t\t\t}\n\t\t\terr := server.FindTraces(&storage.FindTracesRequest{\n\t\t\t\tQuery: &storage.TraceQueryParameters{\n\t\t\t\t\tServiceName:   \"service\",\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t},\n\t\t\t}, stream)\n\t\t\tif test.expectedErr != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedSent, stream.sent)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandler_FindTraceIDs(t *testing.T) {\n\tquery := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service\",\n\t\tOperationName: \"operation\",\n\t\tAttributes:    pcommon.NewMap(),\n\t}\n\tnow := time.Now()\n\ttraceIDA := [16]byte{1}\n\ttraceIDB := [16]byte{2}\n\ttests := []struct {\n\t\tname             string\n\t\ttraceIDs         []tracestore.FoundTraceID\n\t\texpectedTraceIDs []*storage.FoundTraceID\n\t\tfindTraceIDsErr  error\n\t\texpectedErr      error\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\ttraceIDs: []tracestore.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceID: traceIDA,\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(time.Minute),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTraceID: traceIDB,\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(time.Hour),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTraceIDs: []*storage.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceId: traceIDA[:],\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(time.Minute),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTraceId: traceIDB[:],\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(time.Hour),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"empty\",\n\t\t\ttraceIDs:         []tracestore.FoundTraceID{},\n\t\t\texpectedTraceIDs: []*storage.FoundTraceID{},\n\t\t},\n\t\t{\n\t\t\tname:            \"error\",\n\t\t\tfindTraceIDsErr: assert.AnError,\n\t\t\texpectedErr:     assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\treader := new(tracestoremocks.Reader)\n\t\twriter := new(tracestoremocks.Writer)\n\t\tdepReader := new(depstoremocks.Reader)\n\t\treader.On(\"FindTraceIDs\", mock.Anything, query).\n\t\t\tReturn(iter.Seq2[[]tracestore.FoundTraceID, error](func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\t\t\tyield(test.traceIDs, test.findTraceIDsErr)\n\t\t\t})).Once()\n\t\tserver := NewHandler(reader, writer, depReader)\n\n\t\tresponse, err := server.FindTraceIDs(context.Background(), &storage.FindTracesRequest{\n\t\t\tQuery: &storage.TraceQueryParameters{\n\t\t\t\tServiceName:   \"service\",\n\t\t\t\tOperationName: \"operation\",\n\t\t\t},\n\t\t})\n\t\tif test.expectedErr != nil {\n\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t} else {\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, test.expectedTraceIDs, response.TraceIds)\n\t\t}\n\t}\n}\n\nfunc TestHandler_Export(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\twriteTracesErr error\n\t\texpectedErr    error\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t},\n\t\t{\n\t\t\tname:           \"write error\",\n\t\t\texpectedErr:    assert.AnError,\n\t\t\twriteTracesErr: assert.AnError,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\twriter := new(tracestoremocks.Writer)\n\t\t\tdepReader := new(depstoremocks.Reader)\n\t\t\twriter.On(\"WriteTraces\", mock.Anything, makeTestTrace()).Return(test.writeTracesErr).Once()\n\t\t\tserver := NewHandler(reader, writer, depReader)\n\n\t\t\tresponse, err := server.Export(context.Background(), ptraceotlp.NewExportRequestFromTraces(makeTestTrace()))\n\t\t\tif test.expectedErr != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\trequire.Equal(t, ptraceotlp.NewExportResponse(), response)\n\t\t})\n\t}\n}\n\nfunc TestHandler_GetDependencies(t *testing.T) {\n\tnow := time.Now()\n\ttests := []struct {\n\t\tname                 string\n\t\tdependencies         []model.DependencyLink\n\t\texpectedDependencies []*storage.Dependency\n\t\terr                  error\n\t\texpectedError        error\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\tdependencies: []model.DependencyLink{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"serviceA\",\n\t\t\t\t\tChild:     \"serviceB\",\n\t\t\t\t\tCallCount: 10,\n\t\t\t\t\tSource:    \"sourceA\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tParent:    \"serviceC\",\n\t\t\t\t\tChild:     \"serviceD\",\n\t\t\t\t\tCallCount: 20,\n\t\t\t\t\tSource:    \"sourceB\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedDependencies: []*storage.Dependency{\n\t\t\t\t{\n\t\t\t\t\tParent:    \"serviceA\",\n\t\t\t\t\tChild:     \"serviceB\",\n\t\t\t\t\tCallCount: 10,\n\t\t\t\t\tSource:    \"sourceA\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tParent:    \"serviceC\",\n\t\t\t\t\tChild:     \"serviceD\",\n\t\t\t\t\tCallCount: 20,\n\t\t\t\t\tSource:    \"sourceB\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                 \"empty\",\n\t\t\tdependencies:         []model.DependencyLink{},\n\t\t\texpectedDependencies: []*storage.Dependency{},\n\t\t},\n\t\t{\n\t\t\tname:          \"error\",\n\t\t\terr:           assert.AnError,\n\t\t\texpectedError: assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treader := new(tracestoremocks.Reader)\n\t\t\twriter := new(tracestoremocks.Writer)\n\t\t\tdepReader := new(depstoremocks.Reader)\n\t\t\tdepReader.On(\"GetDependencies\", mock.Anything, depstore.QueryParameters{\n\t\t\t\tStartTime: now,\n\t\t\t\tEndTime:   now.Add(time.Minute),\n\t\t\t}).\n\t\t\t\tReturn(test.dependencies, test.err).Once()\n\n\t\t\tserver := NewHandler(reader, writer, depReader)\n\t\t\tresponse, err := server.GetDependencies(context.Background(), &storage.GetDependenciesRequest{\n\t\t\t\tStartTime: now,\n\t\t\t\tEndTime:   now.Add(time.Minute),\n\t\t\t})\n\t\t\tif test.expectedError != nil {\n\t\t\t\trequire.ErrorIs(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedDependencies, response.Dependencies)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConvertKeyValueListToMap(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    []*storage.KeyValue\n\t\texpected pcommon.Map\n\t}{\n\t\t{\n\t\t\tname:     \"empty list\",\n\t\t\tinput:    []*storage.KeyValue{},\n\t\t\texpected: pcommon.NewMap(),\n\t\t},\n\t\t{\n\t\t\tname:     \"nil entry\",\n\t\t\tinput:    []*storage.KeyValue{nil},\n\t\t\texpected: pcommon.NewMap(),\n\t\t},\n\t\t{\n\t\t\tname: \"nil value\",\n\t\t\tinput: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"key1\",\n\t\t\t\t\tValue: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: pcommon.NewMap(),\n\t\t},\n\t\t{\n\t\t\tname: \"primitive types\",\n\t\t\tinput: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{StringValue: \"value1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key2\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_IntValue{IntValue: 42},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key3\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_DoubleValue{DoubleValue: 3.14},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key4\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_BoolValue{BoolValue: true},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key5\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_BytesValue{BytesValue: []byte{1, 2}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tm.PutStr(\"key1\", \"value1\")\n\t\t\t\tm.PutInt(\"key2\", 42)\n\t\t\t\tm.PutDouble(\"key3\", 3.14)\n\t\t\t\tm.PutBool(\"key4\", true)\n\t\t\t\tm.PutEmptyBytes(\"key5\").FromRaw([]byte{1, 2})\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tname: \"nested map\",\n\t\t\tinput: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_KvlistValue{\n\t\t\t\t\t\t\tKvlistValue: &storage.KeyValueList{\n\t\t\t\t\t\t\t\tValues: []*storage.KeyValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tKey: \"nestedKey\",\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{StringValue: \"nestedValue\"},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tKey:   \"nilValueKey\",\n\t\t\t\t\t\t\t\t\t\tValue: nil, // should be skipped\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnil, // should be skipped\n\t\t\t},\n\t\t\texpected: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tnested := m.PutEmptyMap(\"key1\")\n\t\t\t\tnested.PutStr(\"nestedKey\", \"nestedValue\")\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tname: \"array\",\n\t\t\tinput: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_ArrayValue{\n\t\t\t\t\t\t\tArrayValue: &storage.ArrayValue{\n\t\t\t\t\t\t\t\tValues: []*storage.AnyValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{StringValue: \"value1\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_IntValue{IntValue: 42},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_DoubleValue{DoubleValue: 3.14},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_BoolValue{BoolValue: true},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_BytesValue{BytesValue: []byte{1, 2}},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_KvlistValue{\n\t\t\t\t\t\t\t\t\t\t\tKvlistValue: &storage.KeyValueList{\n\t\t\t\t\t\t\t\t\t\t\t\tValues: []*storage.KeyValue{\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tKey: \"nestedKey\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{StringValue: \"nestedValue\"},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tKey:   \"nilValueKey\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tValue: nil, // should be skipped\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tnil, // should be skipped\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnil,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tslice := m.PutEmptySlice(\"key1\")\n\t\t\t\tslice.AppendEmpty().SetStr(\"value1\")\n\t\t\t\tslice.AppendEmpty().SetInt(42)\n\t\t\t\tslice.AppendEmpty().SetDouble(3.14)\n\t\t\t\tslice.AppendEmpty().SetBool(true)\n\t\t\t\tslice.AppendEmpty().SetEmptyBytes().FromRaw([]byte{1, 2})\n\t\t\t\tnested := slice.AppendEmpty().SetEmptyMap()\n\t\t\t\tnested.PutStr(\"nestedKey\", \"nestedValue\")\n\t\t\t\tslice.AppendEmpty() // for the nil entry\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tname: \"nested array\",\n\t\t\tinput: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_ArrayValue{\n\t\t\t\t\t\t\tArrayValue: &storage.ArrayValue{\n\t\t\t\t\t\t\t\tValues: []*storage.AnyValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_ArrayValue{\n\t\t\t\t\t\t\t\t\t\t\tArrayValue: &storage.ArrayValue{\n\t\t\t\t\t\t\t\t\t\t\t\tValues: []*storage.AnyValue{\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{StringValue: \"inner1\"},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tnil,\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnil,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tslice := m.PutEmptySlice(\"key1\")\n\t\t\t\tnestedSlice := slice.AppendEmpty().SetEmptySlice()\n\t\t\t\tnestedSlice.AppendEmpty().SetStr(\"inner1\")\n\t\t\t\tnestedSlice.AppendEmpty() // for the nil entry\n\t\t\t\tslice.AppendEmpty()       // for the nil entry\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tresult := convertKeyValueListToMap(test.input)\n\t\t\tassert.Equal(t, test.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/tracereader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"iter\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar _ tracestore.Reader = (*TraceReader)(nil)\n\ntype TraceReader struct {\n\tclient storage.TraceReaderClient\n}\n\n// NewTraceReader creates a TraceReader that communicates with a remote gRPC storage server.\n// The provided gRPC connection is used exclusively for reading traces, meaning it is safe\n// to enable instrumentation on the connection without risk of recursively generating traces.\nfunc NewTraceReader(conn *grpc.ClientConn) *TraceReader {\n\treturn &TraceReader{\n\t\tclient: storage.NewTraceReaderClient(conn),\n\t}\n}\n\nfunc (tr *TraceReader) GetTraces(\n\tctx context.Context,\n\ttraceIDs ...tracestore.GetTraceParams,\n) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tquery := []*storage.GetTraceParams{}\n\t\tfor _, traceID := range traceIDs {\n\t\t\tquery = append(query, &storage.GetTraceParams{\n\t\t\t\tTraceId:   traceID.TraceID[:],\n\t\t\t\tStartTime: traceID.Start,\n\t\t\t\tEndTime:   traceID.End,\n\t\t\t})\n\t\t}\n\t\tstream, err := tr.client.GetTraces(ctx, &storage.GetTracesRequest{\n\t\t\tQuery: query,\n\t\t})\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to execute GetTraces: %w\", err))\n\t\t\treturn\n\t\t}\n\t\tfor received, err := stream.Recv(); !errors.Is(err, io.EOF); received, err = stream.Recv() {\n\t\t\tif err != nil {\n\t\t\t\tyield(nil, fmt.Errorf(\"received error from grpc stream: %w\", err))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !yield([]ptrace.Traces{received.ToTraces()}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (tr *TraceReader) GetServices(ctx context.Context) ([]string, error) {\n\tresp, err := tr.client.GetServices(ctx, &storage.GetServicesRequest{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to execute GetServices: %w\", err)\n\t}\n\treturn resp.Services, nil\n}\n\nfunc (tr *TraceReader) GetOperations(\n\tctx context.Context,\n\tparams tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\tresp, err := tr.client.GetOperations(ctx, &storage.GetOperationsRequest{\n\t\tService:  params.ServiceName,\n\t\tSpanKind: params.SpanKind,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to execute GetOperations: %w\", err)\n\t}\n\toperations := make([]tracestore.Operation, len(resp.Operations))\n\tfor i, op := range resp.Operations {\n\t\toperations[i] = tracestore.Operation{\n\t\t\tName:     op.Name,\n\t\t\tSpanKind: op.SpanKind,\n\t\t}\n\t}\n\treturn operations, nil\n}\n\nfunc (tr *TraceReader) FindTraces(\n\tctx context.Context,\n\tparams tracestore.TraceQueryParams,\n) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tstream, err := tr.client.FindTraces(ctx, &storage.FindTracesRequest{\n\t\t\tQuery: toProtoQueryParameters(params),\n\t\t})\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to execute FindTraces: %w\", err))\n\t\t\treturn\n\t\t}\n\t\tfor received, err := stream.Recv(); !errors.Is(err, io.EOF); received, err = stream.Recv() {\n\t\t\tif err != nil {\n\t\t\t\tyield(nil, fmt.Errorf(\"received error from grpc stream: %w\", err))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !yield([]ptrace.Traces{received.ToTraces()}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (tr *TraceReader) FindTraceIDs(\n\tctx context.Context,\n\tparams tracestore.TraceQueryParams,\n) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\treturn func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\tresp, err := tr.client.FindTraceIDs(ctx, &storage.FindTracesRequest{\n\t\t\tQuery: toProtoQueryParameters(params),\n\t\t})\n\t\tif err != nil {\n\t\t\tyield(nil, fmt.Errorf(\"failed to execute FindTraceIDs: %w\", err))\n\t\t\treturn\n\t\t}\n\t\tfoundTraceIDs := make([]tracestore.FoundTraceID, len(resp.TraceIds))\n\t\tfor i, foundTraceID := range resp.TraceIds {\n\t\t\tvar sizedTraceID [16]byte\n\t\t\tcopy(sizedTraceID[:], foundTraceID.TraceId)\n\n\t\t\tfoundTraceIDs[i] = tracestore.FoundTraceID{\n\t\t\t\tTraceID: pcommon.TraceID(sizedTraceID),\n\t\t\t\tStart:   foundTraceID.Start,\n\t\t\t\tEnd:     foundTraceID.End,\n\t\t\t}\n\t\t}\n\t\tyield(foundTraceIDs, nil)\n\t}\n}\n\nfunc toProtoQueryParameters(t tracestore.TraceQueryParams) *storage.TraceQueryParameters {\n\treturn &storage.TraceQueryParameters{\n\t\tServiceName:   t.ServiceName,\n\t\tOperationName: t.OperationName,\n\t\tAttributes:    convertMapToKeyValueList(t.Attributes),\n\t\tStartTimeMin:  t.StartTimeMin,\n\t\tStartTimeMax:  t.StartTimeMax,\n\t\tDurationMin:   t.DurationMin,\n\t\tDurationMax:   t.DurationMax,\n\t\tSearchDepth:   int32(t.SearchDepth), //nolint:gosec // G115\n\t}\n}\n\nfunc convertMapToKeyValueList(m pcommon.Map) []*storage.KeyValue {\n\tkeyValues := make([]*storage.KeyValue, 0, m.Len())\n\tm.Range(func(k string, v pcommon.Value) bool {\n\t\tkeyValues = append(keyValues, &storage.KeyValue{\n\t\t\tKey:   k,\n\t\t\tValue: convertValueToAnyValue(v),\n\t\t})\n\t\treturn true\n\t})\n\treturn keyValues\n}\n\nfunc convertValueToAnyValue(v pcommon.Value) *storage.AnyValue {\n\tswitch v.Type() {\n\tcase pcommon.ValueTypeStr:\n\t\treturn &storage.AnyValue{\n\t\t\tValue: &storage.AnyValue_StringValue{\n\t\t\t\tStringValue: v.Str(),\n\t\t\t},\n\t\t}\n\tcase pcommon.ValueTypeBool:\n\t\treturn &storage.AnyValue{\n\t\t\tValue: &storage.AnyValue_BoolValue{\n\t\t\t\tBoolValue: v.Bool(),\n\t\t\t},\n\t\t}\n\tcase pcommon.ValueTypeInt:\n\t\treturn &storage.AnyValue{\n\t\t\tValue: &storage.AnyValue_IntValue{\n\t\t\t\tIntValue: v.Int(),\n\t\t\t},\n\t\t}\n\tcase pcommon.ValueTypeDouble:\n\t\treturn &storage.AnyValue{\n\t\t\tValue: &storage.AnyValue_DoubleValue{\n\t\t\t\tDoubleValue: v.Double(),\n\t\t\t},\n\t\t}\n\tcase pcommon.ValueTypeBytes:\n\t\treturn &storage.AnyValue{\n\t\t\tValue: &storage.AnyValue_BytesValue{\n\t\t\t\tBytesValue: v.Bytes().AsRaw(),\n\t\t\t},\n\t\t}\n\tcase pcommon.ValueTypeSlice:\n\t\tarr := v.Slice()\n\t\tarrayValues := make([]*storage.AnyValue, 0, arr.Len())\n\t\tfor i := 0; i < arr.Len(); i++ {\n\t\t\tarrayValues = append(arrayValues, convertValueToAnyValue(arr.At(i)))\n\t\t}\n\t\treturn &storage.AnyValue{\n\t\t\tValue: &storage.AnyValue_ArrayValue{\n\t\t\t\tArrayValue: &storage.ArrayValue{\n\t\t\t\t\tValues: arrayValues,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\tcase pcommon.ValueTypeMap:\n\t\tkvList := &storage.KeyValueList{}\n\t\tv.Map().Range(func(k string, val pcommon.Value) bool {\n\t\t\tkvList.Values = append(kvList.Values, &storage.KeyValue{\n\t\t\t\tKey:   k,\n\t\t\t\tValue: convertValueToAnyValue(val),\n\t\t\t})\n\t\t\treturn true\n\t\t})\n\t\treturn &storage.AnyValue{Value: &storage.AnyValue_KvlistValue{KvlistValue: kvList}}\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/tracereader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/jaegertracing/jaeger/internal/jiter\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/proto-gen/storage/v2\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\n// testServer implements the storage.TraceReaderServer interface\n// to simulate responses for testing.\ntype testServer struct {\n\tstorage.UnimplementedTraceReaderServer\n\n\ttraces     []*jptrace.TracesData\n\tservices   []string\n\toperations []*storage.Operation\n\ttraceIDs   []*storage.FoundTraceID\n\terr        error\n}\n\nfunc (ts *testServer) GetTraces(_ *storage.GetTracesRequest, s storage.TraceReader_GetTracesServer) error {\n\tfor _, trace := range ts.traces {\n\t\ts.Send(trace)\n\t}\n\treturn ts.err\n}\n\nfunc (ts *testServer) GetServices(\n\tcontext.Context,\n\t*storage.GetServicesRequest,\n) (*storage.GetServicesResponse, error) {\n\treturn &storage.GetServicesResponse{\n\t\tServices: ts.services,\n\t}, ts.err\n}\n\nfunc (ts *testServer) GetOperations(\n\tcontext.Context,\n\t*storage.GetOperationsRequest,\n) (*storage.GetOperationsResponse, error) {\n\treturn &storage.GetOperationsResponse{\n\t\tOperations: ts.operations,\n\t}, ts.err\n}\n\nfunc (ts *testServer) FindTraces(\n\t_ *storage.FindTracesRequest,\n\ts storage.TraceReader_FindTracesServer,\n) error {\n\tfor _, trace := range ts.traces {\n\t\ts.Send(trace)\n\t}\n\treturn ts.err\n}\n\nfunc (ts *testServer) FindTraceIDs(\n\tcontext.Context,\n\t*storage.FindTracesRequest,\n) (*storage.FindTraceIDsResponse, error) {\n\treturn &storage.FindTraceIDsResponse{\n\t\tTraceIds: ts.traceIDs,\n\t}, ts.err\n}\n\nfunc startTestServer(t *testing.T, testServer *testServer) *grpc.ClientConn {\n\tlistener, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err)\n\n\tserver := grpc.NewServer()\n\tstorage.RegisterTraceReaderServer(server, testServer)\n\n\treturn startServer(t, server, listener)\n}\n\nfunc startServer(t *testing.T, server *grpc.Server, listener net.Listener) *grpc.ClientConn {\n\tgo func() {\n\t\tserver.Serve(listener)\n\t}()\n\n\tconn, err := grpc.NewClient(\n\t\tlistener.Addr().String(),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(\n\t\tfunc() {\n\t\t\tconn.Close()\n\t\t\tserver.Stop()\n\t\t\tlistener.Close()\n\t\t},\n\t)\n\n\treturn conn\n}\n\nfunc makeTestTrace() ptrace.Traces {\n\ttrace := ptrace.NewTraces()\n\tresources := trace.ResourceSpans().AppendEmpty()\n\tscopes := resources.ScopeSpans().AppendEmpty()\n\n\tspanA := scopes.Spans().AppendEmpty()\n\tspanA.SetName(\"foobar\")\n\tspanA.SetTraceID(pcommon.TraceID([16]byte{1}))\n\tspanA.SetSpanID(pcommon.SpanID([8]byte{2}))\n\tspanA.SetKind(ptrace.SpanKindServer)\n\tspanA.Status().SetCode(ptrace.StatusCodeError)\n\n\treturn trace\n}\n\nfunc TestTraceReader_GetTraces(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\ttestServer     *testServer\n\t\ttraces         []*jptrace.TracesData\n\t\texpectedTraces []ptrace.Traces\n\t\texpectedError  string\n\t}{\n\t\t{\n\t\t\tname: \"single trace\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraces: func() []*jptrace.TracesData {\n\t\t\t\t\ttrace := makeTestTrace()\n\t\t\t\t\ttraces := []*jptrace.TracesData{(*jptrace.TracesData)(&trace)}\n\t\t\t\t\treturn traces\n\t\t\t\t}(),\n\t\t\t},\n\t\t\texpectedTraces: []ptrace.Traces{makeTestTrace()},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple traces\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraces: func() []*jptrace.TracesData {\n\t\t\t\t\ttraceA := makeTestTrace()\n\t\t\t\t\ttraceB := makeTestTrace()\n\t\t\t\t\ttraces := []*jptrace.TracesData{\n\t\t\t\t\t\t(*jptrace.TracesData)(&traceA),\n\t\t\t\t\t\t(*jptrace.TracesData)(&traceB),\n\t\t\t\t\t}\n\t\t\t\t\treturn traces\n\t\t\t\t}(),\n\t\t\t},\n\t\t\texpectedTraces: []ptrace.Traces{makeTestTrace(), makeTestTrace()},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraces: func() []*jptrace.TracesData {\n\t\t\t\t\ttrace := ptrace.NewTraces()\n\t\t\t\t\ttraces := []*jptrace.TracesData{(*jptrace.TracesData)(&trace)}\n\t\t\t\t\treturn traces\n\t\t\t\t}(),\n\t\t\t\terr: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"received error from grpc stream\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn := startTestServer(t, test.testServer)\n\n\t\t\treader := NewTraceReader(conn)\n\t\t\tgetTracesIter := reader.GetTraces(context.Background(), tracestore.GetTraceParams{})\n\t\t\ttraces, err := jiter.FlattenWithErrors(getTracesIter)\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedTraces, traces)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_GetTraces_YieldStopsIteration(t *testing.T) {\n\ttraceA := makeTestTrace()\n\ttraceB := makeTestTrace()\n\ttestServer := &testServer{\n\t\ttraces: []*jptrace.TracesData{\n\t\t\t(*jptrace.TracesData)(&traceA),\n\t\t\t(*jptrace.TracesData)(&traceB),\n\t\t},\n\t}\n\n\tconn := startTestServer(t, testServer)\n\treader := NewTraceReader(conn)\n\n\tgetTracesIter := reader.GetTraces(context.Background(), tracestore.GetTraceParams{})\n\tvar gotTraces []ptrace.Traces\n\tgetTracesIter(func(traces []ptrace.Traces, _ error) bool {\n\t\tgotTraces = append(gotTraces, traces...)\n\t\treturn false\n\t})\n\n\trequire.Len(t, gotTraces, 1)\n}\n\nfunc TestTraceReader_GetTraces_GRPCClientError(t *testing.T) {\n\tconn, err := grpc.NewClient(\":0\",\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t) // create client without a started server\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tconn.Close()\n\t})\n\treader := NewTraceReader(conn)\n\tgetTracesIter := reader.GetTraces(context.Background(), tracestore.GetTraceParams{})\n\t_, err = jiter.FlattenWithErrors(getTracesIter)\n\trequire.ErrorContains(t, err, \"failed to execute GetTraces\")\n}\n\nfunc TestTraceReader_GetServices(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\ttestServer       *testServer\n\t\texpectedServices []string\n\t\texpectedError    string\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\ttestServer: &testServer{\n\t\t\t\tservices: []string{\"service-a\", \"service-b\"},\n\t\t\t},\n\t\t\texpectedServices: []string{\"service-a\", \"service-b\"},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\ttestServer: &testServer{\n\t\t\t\terr: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to execute GetServices\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn := startTestServer(t, test.testServer)\n\n\t\t\treader := NewTraceReader(conn)\n\t\t\tservices, err := reader.GetServices(context.Background())\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, test.expectedServices, services)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_GetOperations(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\ttestServer    *testServer\n\t\texpectedOps   []tracestore.Operation\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\ttestServer: &testServer{\n\t\t\t\toperations: []*storage.Operation{\n\t\t\t\t\t{Name: \"operation-a\", SpanKind: \"kind\"},\n\t\t\t\t\t{Name: \"operation-b\", SpanKind: \"kind\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedOps: []tracestore.Operation{\n\t\t\t\t{Name: \"operation-a\", SpanKind: \"kind\"},\n\t\t\t\t{Name: \"operation-b\", SpanKind: \"kind\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\ttestServer: &testServer{\n\t\t\t\terr: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"failed to execute GetOperations\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn := startTestServer(t, test.testServer)\n\n\t\t\treader := NewTraceReader(conn)\n\t\t\tops, err := reader.GetOperations(context.Background(), tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"service-a\",\n\t\t\t\tSpanKind:    \"kind\",\n\t\t\t})\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, test.expectedOps, ops)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_FindTraces(t *testing.T) {\n\tqueryParams := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service-a\",\n\t\tOperationName: \"operation-a\",\n\t\tAttributes:    pcommon.NewMap(),\n\t}\n\ttests := []struct {\n\t\tname           string\n\t\ttestServer     *testServer\n\t\ttraces         []*jptrace.TracesData\n\t\texpectedTraces []ptrace.Traces\n\t\texpectedError  string\n\t}{\n\t\t{\n\t\t\tname: \"single trace\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraces: func() []*jptrace.TracesData {\n\t\t\t\t\ttrace := makeTestTrace()\n\t\t\t\t\ttraces := []*jptrace.TracesData{(*jptrace.TracesData)(&trace)}\n\t\t\t\t\treturn traces\n\t\t\t\t}(),\n\t\t\t},\n\t\t\texpectedTraces: []ptrace.Traces{makeTestTrace()},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple traces\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraces: func() []*jptrace.TracesData {\n\t\t\t\t\ttraceA := makeTestTrace()\n\t\t\t\t\ttraceB := makeTestTrace()\n\t\t\t\t\ttraces := []*jptrace.TracesData{\n\t\t\t\t\t\t(*jptrace.TracesData)(&traceA),\n\t\t\t\t\t\t(*jptrace.TracesData)(&traceB),\n\t\t\t\t\t}\n\t\t\t\t\treturn traces\n\t\t\t\t}(),\n\t\t\t},\n\t\t\texpectedTraces: []ptrace.Traces{makeTestTrace(), makeTestTrace()},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraces: func() []*jptrace.TracesData {\n\t\t\t\t\ttrace := ptrace.NewTraces()\n\t\t\t\t\ttraces := []*jptrace.TracesData{(*jptrace.TracesData)(&trace)}\n\t\t\t\t\treturn traces\n\t\t\t\t}(),\n\t\t\t\terr: assert.AnError,\n\t\t\t},\n\t\t\texpectedError: \"received error from grpc stream\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn := startTestServer(t, test.testServer)\n\n\t\t\treader := NewTraceReader(conn)\n\t\t\tgetTracesIter := reader.FindTraces(context.Background(), queryParams)\n\t\t\ttraces, err := jiter.FlattenWithErrors(getTracesIter)\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedTraces, traces)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_FindTraces_YieldStopsIteration(t *testing.T) {\n\tqueryParams := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service-a\",\n\t\tOperationName: \"operation-a\",\n\t\tAttributes:    pcommon.NewMap(),\n\t}\n\ttraceA := makeTestTrace()\n\ttraceB := makeTestTrace()\n\ttestServer := &testServer{\n\t\ttraces: []*jptrace.TracesData{\n\t\t\t(*jptrace.TracesData)(&traceA),\n\t\t\t(*jptrace.TracesData)(&traceB),\n\t\t},\n\t}\n\n\tconn := startTestServer(t, testServer)\n\treader := NewTraceReader(conn)\n\n\tgetTracesIter := reader.FindTraces(context.Background(), queryParams)\n\tvar gotTraces []ptrace.Traces\n\tgetTracesIter(func(traces []ptrace.Traces, _ error) bool {\n\t\tgotTraces = append(gotTraces, traces...)\n\t\treturn false\n\t})\n\n\trequire.Len(t, gotTraces, 1)\n}\n\nfunc TestTraceReader_FindTraces_GRPCClientError(t *testing.T) {\n\tqueryParams := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service-a\",\n\t\tOperationName: \"operation-a\",\n\t\tAttributes:    pcommon.NewMap(),\n\t}\n\tconn, err := grpc.NewClient(\":0\",\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t) // create client without a started server\n\trequire.NoError(t, err)\n\tt.Cleanup(func() {\n\t\tconn.Close()\n\t})\n\treader := NewTraceReader(conn)\n\tgetTracesIter := reader.FindTraces(context.Background(), queryParams)\n\t_, err = jiter.FlattenWithErrors(getTracesIter)\n\trequire.ErrorContains(t, err, \"failed to execute FindTraces\")\n}\n\nfunc TestTraceReader_FindTraceIDs(t *testing.T) {\n\tqueryParams := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service-a\",\n\t\tOperationName: \"operation-a\",\n\t\tAttributes:    pcommon.NewMap(),\n\t}\n\tnow := time.Now().UTC()\n\ttests := []struct {\n\t\tname          string\n\t\ttestServer    *testServer\n\t\tqueryParams   tracestore.TraceQueryParams\n\t\texpectedIDs   []tracestore.FoundTraceID\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraceIDs: []*storage.FoundTraceID{\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceId: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},\n\t\t\t\t\t\tStart:   now,\n\t\t\t\t\t\tEnd:     now.Add(1 * time.Second),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceId: []byte{2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},\n\t\t\t\t\t\tStart:   now,\n\t\t\t\t\t\tEnd:     now.Add(1 * time.Minute),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tqueryParams: queryParams,\n\t\t\texpectedIDs: []tracestore.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceID: pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(1 * time.Second),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTraceID: pcommon.TraceID([16]byte{2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(1 * time.Minute),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"trace ID with less than 16 bytes\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraceIDs: []*storage.FoundTraceID{\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceId: []byte{1, 2, 3, 4, 5, 6, 7, 8},\n\t\t\t\t\t\tStart:   now,\n\t\t\t\t\t\tEnd:     now.Add(1 * time.Second),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tqueryParams: queryParams,\n\t\t\texpectedIDs: []tracestore.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceID: pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8}),\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(1 * time.Second),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"trace ID with more than 16 bytes\",\n\t\t\ttestServer: &testServer{\n\t\t\t\ttraceIDs: []*storage.FoundTraceID{\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceId: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},\n\t\t\t\t\t\tStart:   now,\n\t\t\t\t\t\tEnd:     now.Add(1 * time.Second),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tqueryParams: queryParams,\n\t\t\texpectedIDs: []tracestore.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceID: pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),\n\t\t\t\t\tStart:   now,\n\t\t\t\t\tEnd:     now.Add(1 * time.Second),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"error\",\n\t\t\ttestServer: &testServer{\n\t\t\t\terr: assert.AnError,\n\t\t\t},\n\t\t\tqueryParams:   queryParams,\n\t\t\texpectedError: \"failed to execute FindTraceIDs\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn := startTestServer(t, test.testServer)\n\n\t\t\treader := NewTraceReader(conn)\n\n\t\t\tfoundIDsIter := reader.FindTraceIDs(context.Background(), test.queryParams)\n\t\t\tfoundIDs, err := jiter.FlattenWithErrors(foundIDsIter)\n\n\t\t\tif test.expectedError != \"\" {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedError)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, test.expectedIDs, foundIDs)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConvertMapToKeyValueList(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tattributes pcommon.Map\n\t\texpected   []*storage.KeyValue\n\t}{\n\t\t{\n\t\t\tname:       \"empty map\",\n\t\t\tattributes: pcommon.NewMap(),\n\t\t\texpected:   []*storage.KeyValue{},\n\t\t},\n\t\t{\n\t\t\tname: \"empty value\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tm.PutEmpty(\"key1\")\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey:   \"key1\",\n\t\t\t\t\tValue: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"primitive types\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tm.PutStr(\"key1\", \"value1\")\n\t\t\t\tm.PutInt(\"key2\", 42)\n\t\t\t\tm.PutDouble(\"key3\", 3.14)\n\t\t\t\tm.PutBool(\"key4\", true)\n\t\t\t\tm.PutEmptyBytes(\"key5\").Append(1, 2)\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{\n\t\t\t\t\t\t\tStringValue: \"value1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key2\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_IntValue{\n\t\t\t\t\t\t\tIntValue: 42,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key3\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_DoubleValue{\n\t\t\t\t\t\t\tDoubleValue: 3.14,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key4\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_BoolValue{\n\t\t\t\t\t\t\tBoolValue: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey: \"key5\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_BytesValue{\n\t\t\t\t\t\t\tBytesValue: []byte{1, 2},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"nested map\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tnested := pcommon.NewMap()\n\t\t\t\tnested.PutStr(\"nestedKey\", \"nestedValue\")\n\t\t\t\tnested.CopyTo(m.PutEmptyMap(\"key1\"))\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_KvlistValue{\n\t\t\t\t\t\t\tKvlistValue: &storage.KeyValueList{\n\t\t\t\t\t\t\t\tValues: []*storage.KeyValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tKey: \"nestedKey\",\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{\n\t\t\t\t\t\t\t\t\t\t\t\tStringValue: \"nestedValue\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"array attribute\",\n\t\t\tattributes: func() pcommon.Map {\n\t\t\t\tm := pcommon.NewMap()\n\t\t\t\tarr := pcommon.NewValueSlice()\n\t\t\t\tarr.Slice().AppendEmpty().SetStr(\"value1\")\n\t\t\t\tarr.Slice().AppendEmpty().SetInt(42)\n\t\t\t\tarr.Slice().CopyTo(m.PutEmptySlice(\"key1\"))\n\t\t\t\treturn m\n\t\t\t}(),\n\t\t\texpected: []*storage.KeyValue{\n\t\t\t\t{\n\t\t\t\t\tKey: \"key1\",\n\t\t\t\t\tValue: &storage.AnyValue{\n\t\t\t\t\t\tValue: &storage.AnyValue_ArrayValue{\n\t\t\t\t\t\t\tArrayValue: &storage.ArrayValue{\n\t\t\t\t\t\t\t\tValues: []*storage.AnyValue{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_StringValue{\n\t\t\t\t\t\t\t\t\t\t\tStringValue: \"value1\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tValue: &storage.AnyValue_IntValue{\n\t\t\t\t\t\t\t\t\t\t\tIntValue: 42,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tresult := convertMapToKeyValueList(test.attributes)\n\t\t\trequire.Equal(t, test.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/tracewriter.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar _ tracestore.Writer = (*TraceWriter)(nil)\n\ntype TraceWriter struct {\n\tclient ptraceotlp.GRPCClient\n}\n\n// NewTraceWriter creates a TraceWriter that exports traces to a remote gRPC storage server.\n//\n// The provided gRPC connection is used exclusively for sending trace data to the backend.\n// To prevent recursive trace generation, this connection should not have instrumentation enabled.\nfunc NewTraceWriter(conn *grpc.ClientConn) *TraceWriter {\n\treturn &TraceWriter{\n\t\tclient: ptraceotlp.NewGRPCClient(conn),\n\t}\n}\n\nfunc (tw *TraceWriter) WriteTraces(ctx context.Context, td ptrace.Traces) error {\n\treq := ptraceotlp.NewExportRequestFromTraces(td)\n\t_, err := tw.client.Export(ctx, req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to export traces: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/storage/v2/grpc/tracewriter_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage grpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\ntype testWriterServer struct {\n\tptraceotlp.UnimplementedGRPCServer\n\n\terr error\n}\n\nfunc (s *testWriterServer) Export(\n\tcontext.Context,\n\tptraceotlp.ExportRequest,\n) (ptraceotlp.ExportResponse, error) {\n\treturn ptraceotlp.NewExportResponse(), s.err\n}\n\nfunc startWriterServer(t *testing.T, testServer *testWriterServer) *grpc.ClientConn {\n\tlistener, err := net.Listen(\"tcp\", \":0\")\n\trequire.NoError(t, err)\n\n\tserver := grpc.NewServer()\n\tptraceotlp.RegisterGRPCServer(server, testServer)\n\n\tgo func() {\n\t\tserver.Serve(listener)\n\t}()\n\n\tconn, err := grpc.NewClient(\n\t\tlistener.Addr().String(),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t)\n\trequire.NoError(t, err)\n\n\tt.Cleanup(\n\t\tfunc() {\n\t\t\tconn.Close()\n\t\t\tserver.Stop()\n\t\t\tlistener.Close()\n\t\t},\n\t)\n\n\treturn conn\n}\n\nfunc TestTraceWriter_WriteTraces(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tserverErr   error\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"no error\",\n\t\t},\n\t\t{\n\t\t\tname:        \"server error\",\n\t\t\tserverErr:   assert.AnError,\n\t\t\texpectedErr: \"failed to export traces\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tserver := &testWriterServer{err: test.serverErr}\n\t\t\tconn := startWriterServer(t, server)\n\n\t\t\twriter := NewTraceWriter(conn)\n\t\t\terr := writer.WriteTraces(context.Background(), ptrace.NewTraces())\n\t\t\tif test.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, test.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/config.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport \"github.com/asaskevich/govalidator\"\n\n// Configuration describes the options to customize the storage behavior.\ntype Configuration struct {\n\t// MaxTraces is the maximum amount of traces to store in memory.\n\t// If multi-tenancy is enabled, this limit applies per tenant.\n\t// Zero value (default) means no limit (Warning: memory usage will be unbounded).\n\tMaxTraces int `mapstructure:\"max_traces\"`\n}\n\nfunc (c *Configuration) Validate() error {\n\t_, err := govalidator.ValidateStruct(c)\n\treturn err\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/config_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidate_DoesNotReturnErrorWhenValid(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tconfig *Configuration\n\t}{\n\t\t{\n\t\t\tname:   \"non-required fields not set\",\n\t\t\tconfig: &Configuration{},\n\t\t},\n\t\t{\n\t\t\tname: \"all fields are set\",\n\t\t\tconfig: &Configuration{\n\t\t\t\tMaxTraces: 100,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\terr := test.config.Validate()\n\t\t\trequire.NoError(t, err)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger/internal/distributedlock\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/tracestoremetrics\"\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nvar (\n\t_ tracestore.Factory           = (*Factory)(nil)\n\t_ storage.SamplingStoreFactory = (*Factory)(nil)\n\t_ storage.Purger               = (*Factory)(nil)\n)\n\ntype Factory struct {\n\tstore          *Store\n\tmetricsFactory metrics.Factory\n}\n\nfunc NewFactory(cfg Configuration, telset telemetry.Settings) (*Factory, error) {\n\tstore, err := NewStore(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Factory{\n\t\tstore:          store,\n\t\tmetricsFactory: telset.Metrics,\n\t}, nil\n}\n\nfunc (f *Factory) CreateTraceReader() (tracestore.Reader, error) {\n\treturn tracestoremetrics.NewReaderDecorator(f.store, f.metricsFactory), nil\n}\n\nfunc (f *Factory) CreateTraceWriter() (tracestore.Writer, error) {\n\treturn f.store, nil\n}\n\nfunc (f *Factory) CreateDependencyReader() (depstore.Reader, error) {\n\treturn f.store, nil\n}\n\nfunc (*Factory) CreateSamplingStore(buckets int) (samplingstore.Store, error) {\n\treturn NewSamplingStore(buckets), nil\n}\n\nfunc (*Factory) CreateLock() (distributedlock.Lock, error) {\n\treturn &Lock{}, nil\n}\n\nfunc (f *Factory) Purge(_ context.Context) error {\n\treturn f.store.Purge()\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n)\n\nfunc TestNewFactory(t *testing.T) {\n\tf, err := NewFactory(Configuration{MaxTraces: 10}, telemetry.NoopSettings())\n\trequire.NoError(t, err)\n\t_, err = f.CreateTraceWriter()\n\trequire.NoError(t, err)\n\t_, err = f.CreateTraceReader()\n\trequire.NoError(t, err)\n\t_, err = f.CreateDependencyReader()\n\trequire.NoError(t, err)\n\t_, err = f.CreateSamplingStore(5)\n\trequire.NoError(t, err)\n\t_, err = f.CreateLock()\n\trequire.NoError(t, err)\n\trequire.NoError(t, f.Purge(context.Background()))\n}\n\nfunc TestNewFactoryErr(t *testing.T) {\n\tf, err := NewFactory(Configuration{}, telemetry.NoopSettings())\n\trequire.ErrorContains(t, err, \"max traces must be greater than zero\")\n\tassert.Nil(t, f)\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/fixtures/db_traces_01.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"service-x\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {\n            \"name\": \"testing-library-2\",\n            \"version\": \"1.1.1\",\n            \"attributes\": [\n              {\n                \"key\": \"scope.attributes.2\",\n                \"value\": {\n                  \"stringValue\": \"attribute-y\"\n                }\n              }\n            ]\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000010000000000000000\",\n              \"spanId\": \"0000000000000002\",\n              \"parentSpanId\": \"0000000000000003\",\n              \"kind\": 2,\n              \"traceState\": \"state.2\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-2\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        },\n        {\n          \"scope\": {\n            \"name\": \"testing-library-3\",\n            \"version\": \"1.1.2\",\n            \"attributes\": [\n              {\n                \"key\": \"scope.attributes.3\",\n                \"value\": {\n                  \"stringValue\": \"attribute-y\"\n                }\n              }\n            ]\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000010000000000000000\",\n              \"spanId\": \"0000000000000004\",\n              \"parentSpanId\": \"0000000000000011\",\n              \"kind\": 4,\n              \"traceState\": \"state.4\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-4\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/v2/memory/fixtures/db_traces_02.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"service-x\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {\n            \"name\": \"testing-library-2\",\n            \"version\": \"1.1.1\",\n            \"attributes\": [\n              {\n                \"key\": \"scope.attributes.2\",\n                \"value\": {\n                  \"stringValue\": \"attribute-y\"\n                }\n              }\n            ]\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000020000000000000000\",\n              \"spanId\": \"0000000000000003\",\n              \"parentSpanId\": \"0000000000000010\",\n              \"kind\": 3,\n              \"traceState\": \"state.3\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-3\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        },\n        {\n          \"scope\": {\n            \"name\": \"testing-library-3\",\n            \"version\": \"1.1.2\",\n            \"attributes\": [\n              {\n                \"key\": \"scope.attributes.3\",\n                \"value\": {\n                  \"stringValue\": \"attribute-y\"\n                }\n              }\n            ]\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000020000000000000000\",\n              \"spanId\": \"0000000000000005\",\n              \"parentSpanId\": \"0000000000000010\",\n              \"kind\": 5,\n              \"traceState\": \"state.5\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-5\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/v2/memory/fixtures/otel_traces_01.json",
    "content": "{\n  \"resourceSpans\": [\n    {\n      \"resource\": {\n        \"attributes\": [\n          {\n            \"key\": \"service.name\",\n            \"value\": {\n              \"stringValue\": \"service-x\"\n            }\n          }\n        ]\n      },\n      \"scopeSpans\": [\n        {\n          \"scope\": {\n            \"name\": \"testing-library-2\",\n            \"version\": \"1.1.1\",\n            \"attributes\": [\n              {\n                \"key\": \"scope.attributes.2\",\n                \"value\": {\n                  \"stringValue\": \"attribute-y\"\n                }\n              }\n            ]\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000010000000000000000\",\n              \"spanId\": \"0000000000000002\",\n              \"parentSpanId\": \"0000000000000003\",\n              \"kind\": 2,\n              \"traceState\": \"state.2\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-2\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            },\n            {\n              \"traceId\": \"00000000000000020000000000000000\",\n              \"spanId\": \"0000000000000003\",\n              \"parentSpanId\": \"0000000000000010\",\n              \"kind\": 3,\n              \"traceState\": \"state.3\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-3\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        },\n        {\n          \"scope\": {\n            \"name\": \"testing-library-3\",\n            \"version\": \"1.1.2\",\n            \"attributes\": [\n              {\n                \"key\": \"scope.attributes.3\",\n                \"value\": {\n                  \"stringValue\": \"attribute-y\"\n                }\n              }\n            ]\n          },\n          \"spans\": [\n            {\n              \"traceId\": \"00000000000000010000000000000000\",\n              \"spanId\": \"0000000000000004\",\n              \"parentSpanId\": \"0000000000000011\",\n              \"kind\": 4,\n              \"traceState\": \"state.4\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-4\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            },\n            {\n              \"traceId\": \"00000000000000020000000000000000\",\n              \"spanId\": \"0000000000000005\",\n              \"parentSpanId\": \"0000000000000010\",\n              \"kind\": 5,\n              \"traceState\": \"state.5\",\n              \"flags\": 1,\n              \"name\": \"test-general-conversion-5\",\n              \"startTimeUnixNano\": \"1485467191639875000\",\n              \"endTimeUnixNano\": \"1485467191639880000\",\n              \"attributes\": [\n                {\n                  \"key\": \"peer.service\",\n                  \"value\": {\n                    \"stringValue\": \"service-y\"\n                  }\n                },\n                {\n                  \"key\": \"peer.ipv4\",\n                  \"value\": {\n                    \"intValue\": \"23456\"\n                  }\n                },\n                {\n                  \"key\": \"blob\",\n                  \"value\": {\n                    \"bytesValue\": \"AAAwOQ==\"\n                  }\n                },\n                {\n                  \"key\": \"temperature\",\n                  \"value\": {\n                    \"doubleValue\": 72.5\n                  }\n                }\n              ],\n              \"events\": [\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"name\": \"testing-event\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"event-x\",\n                      \"value\": {\n                        \"stringValue\": \"event-y\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"timeUnixNano\": \"1485467191639875000\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"x\",\n                      \"value\": {\n                        \"stringValue\": \"y\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"links\": [\n                {\n                  \"traceId\": \"00000000000000010000000000000000\",\n                  \"spanId\": \"0000000000000004\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"follows_from\"\n                      }\n                    }\n                  ]\n                },\n                {\n                  \"traceId\": \"00000000000000ff0000000000000000\",\n                  \"spanId\": \"00000000000000ff\",\n                  \"attributes\": [\n                    {\n                      \"key\": \"opentracing.ref_type\",\n                      \"value\": {\n                        \"stringValue\": \"child_of\"\n                      }\n                    }\n                  ]\n                }\n              ],\n              \"status\": {\n                \"code\": 2\n              }\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "internal/storage/v2/memory/lock.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport \"time\"\n\ntype Lock struct{}\n\n// Acquire always returns true for memory storage because it's a single-node\nfunc (*Lock) Acquire(string /* resource */, time.Duration /* ttl */) (bool, error) {\n\treturn true, nil\n}\n\n// Forfeit always returns true for memory storage\nfunc (*Lock) Forfeit(string /* resource */) (bool, error) {\n\treturn true, nil\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/lock_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAcquire(t *testing.T) {\n\tl := &Lock{}\n\tok, err := l.Acquire(\"resource\", time.Duration(1))\n\tassert.True(t, ok)\n\trequire.NoError(t, err)\n}\n\nfunc TestForfeit(t *testing.T) {\n\tl := &Lock{}\n\tok, err := l.Forfeit(\"resource\")\n\tassert.True(t, ok)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/memory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"iter\"\n\t\"sync\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tconventions \"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\nconst errorAttribute = \"error\"\n\nvar errInvalidSearchDepth = errors.New(\"search depth must be greater than 0 and less than max traces\")\n\n// Store is an in-memory store of traces\ntype Store struct {\n\tmu sync.RWMutex\n\t// Each tenant gets a copy of default config.\n\t// In the future this can be extended to contain per-tenant configuration.\n\tcfg       Configuration\n\tperTenant map[string]*Tenant\n}\n\n// NewStore creates an in-memory store\nfunc NewStore(cfg Configuration) (*Store, error) {\n\tif cfg.MaxTraces <= 0 {\n\t\treturn nil, errInvalidMaxTraces\n\t}\n\treturn &Store{\n\t\tcfg:       cfg,\n\t\tperTenant: make(map[string]*Tenant),\n\t}, nil\n}\n\n// getTenant returns the per-tenant storage.  Note that tenantID has already been checked for by the collector or query\nfunc (st *Store) getTenant(tenantID string) *Tenant {\n\tst.mu.RLock()\n\ttenant, ok := st.perTenant[tenantID]\n\tst.mu.RUnlock()\n\tif !ok {\n\t\tst.mu.Lock()\n\t\tdefer st.mu.Unlock()\n\t\ttenant, ok = st.perTenant[tenantID]\n\t\tif !ok {\n\t\t\ttenant = newTenant(&st.cfg)\n\t\t\tst.perTenant[tenantID] = tenant\n\t\t}\n\t}\n\treturn tenant\n}\n\n// WriteTraces write the traces into the tenant by grouping all the spans with same trace id together.\n// The traces will not be saved as they are coming, rather they would be reshuffled.\nfunc (st *Store) WriteTraces(ctx context.Context, td ptrace.Traces) error {\n\tresourceSpansByTraceId := reshuffleResourceSpans(td.ResourceSpans())\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\tm.storeTraces(resourceSpansByTraceId)\n\treturn nil\n}\n\n// GetOperations returns operations based on the service name and span kind\nfunc (st *Store) GetOperations(ctx context.Context, query tracestore.OperationQueryParams) ([]tracestore.Operation, error) {\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\tm.mu.RLock()\n\tdefer m.mu.RUnlock()\n\tvar retMe []tracestore.Operation\n\tif operations, ok := m.operations[query.ServiceName]; ok {\n\t\tfor operation := range operations {\n\t\t\tif query.SpanKind == \"\" || query.SpanKind == operation.SpanKind {\n\t\t\t\tretMe = append(retMe, operation)\n\t\t\t}\n\t\t}\n\t}\n\treturn retMe, nil\n}\n\n// GetServices returns a list of all known services\nfunc (st *Store) GetServices(ctx context.Context) ([]string, error) {\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\tm.mu.RLock()\n\tdefer m.mu.RUnlock()\n\tvar retMe []string\n\tfor k := range m.services {\n\t\tretMe = append(retMe, k)\n\t}\n\treturn retMe, nil\n}\n\nfunc (st *Store) FindTraces(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]ptrace.Traces, error] {\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\ttraceAndIds, err := m.findTraceAndIds(query)\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\tfor i := range traceAndIds {\n\t\t\tif !yield([]ptrace.Traces{traceAndIds[i].trace}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (st *Store) FindTraceIDs(ctx context.Context, query tracestore.TraceQueryParams) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\treturn func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\ttraceAndIds, err := m.findTraceAndIds(query)\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\tids := make([]tracestore.FoundTraceID, len(traceAndIds))\n\t\tfor i := range traceAndIds {\n\t\t\tids[i] = tracestore.FoundTraceID{TraceID: traceAndIds[i].id}\n\t\t}\n\t\tyield(ids, nil)\n\t}\n}\n\nfunc (st *Store) GetTraces(ctx context.Context, traceIDs ...tracestore.GetTraceParams) iter.Seq2[[]ptrace.Traces, error] {\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\ttraces := m.getTraces(traceIDs...)\n\t\tfor i := range traces {\n\t\t\tif !yield([]ptrace.Traces{traces[i]}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (st *Store) GetDependencies(ctx context.Context, query depstore.QueryParameters) ([]model.DependencyLink, error) {\n\tm := st.getTenant(tenancy.GetTenant(ctx))\n\treturn m.getDependencies(query)\n}\n\nfunc (st *Store) Purge() error {\n\tst.mu.Lock()\n\tst.perTenant = make(map[string]*Tenant)\n\tst.mu.Unlock()\n\treturn nil\n}\n\n// reshuffleResourceSpans reshuffles the resource spans so as to group the spans from same traces together. To understand this reshuffling\n// take an example of 2 resource spans, then these two resource spans have 2 scope spans each.\n// Every scope span consists of 2 spans with trace ids: 1 and 2. Now the final structure should look like:\n// For TraceID1: [ResourceSpan1:[ScopeSpan1:[Span(TraceID1)],ScopeSpan2:[Span(TraceID1)], ResourceSpan2:[ScopeSpan1:[Span(TraceID1)],ScopeSpan2:[Span(TraceID1)]\n// A similar structure will be there for TraceID2\nfunc reshuffleResourceSpans(resourceSpanSlice ptrace.ResourceSpansSlice) map[pcommon.TraceID]ptrace.ResourceSpansSlice {\n\tresourceSpansByTraceId := make(map[pcommon.TraceID]ptrace.ResourceSpansSlice)\n\tfor _, resourceSpan := range resourceSpanSlice.All() {\n\t\tscopeSpansByTraceId := reshuffleScopeSpans(resourceSpan.ScopeSpans())\n\t\t// All the  scope spans here will have the same resource as of resourceSpan. Therefore:\n\t\t// Copy the resource to an empty resourceSpan. After this, append the scope spans with same\n\t\t// trace id to this empty resource span. Finally move this resource span to the resourceSpanSlice\n\t\t// containing other resource spans and having same trace id.\n\t\tfor traceId, scopeSpansSlice := range scopeSpansByTraceId {\n\t\t\tresourceSpanByTraceId := ptrace.NewResourceSpans()\n\t\t\tresourceSpan.Resource().CopyTo(resourceSpanByTraceId.Resource())\n\t\t\tscopeSpansSlice.MoveAndAppendTo(resourceSpanByTraceId.ScopeSpans())\n\t\t\tresourceSpansSlice, ok := resourceSpansByTraceId[traceId]\n\t\t\tif !ok {\n\t\t\t\tresourceSpansSlice = ptrace.NewResourceSpansSlice()\n\t\t\t\tresourceSpansByTraceId[traceId] = resourceSpansSlice\n\t\t\t}\n\t\t\tresourceSpanByTraceId.MoveTo(resourceSpansSlice.AppendEmpty())\n\t\t}\n\t}\n\treturn resourceSpansByTraceId\n}\n\n// reshuffleScopeSpans reshuffles all the scope spans of a resource span to group\n// spans of same trace ids together. The first step is to iterate the scope spans and then.\n// copy the scope to an empty scopeSpan. After this, append the spans with same\n// trace id to this empty scope span. Finally move this scope span to the scope span\n// slice containing other scope spans and having same trace id.\nfunc reshuffleScopeSpans(scopeSpanSlice ptrace.ScopeSpansSlice) map[pcommon.TraceID]ptrace.ScopeSpansSlice {\n\tscopeSpansByTraceId := make(map[pcommon.TraceID]ptrace.ScopeSpansSlice)\n\tfor _, scopeSpan := range scopeSpanSlice.All() {\n\t\tspansByTraceId := reshuffleSpans(scopeSpan.Spans())\n\t\tfor traceId, spansSlice := range spansByTraceId {\n\t\t\tscopeSpanByTraceId := ptrace.NewScopeSpans()\n\t\t\tscopeSpan.Scope().CopyTo(scopeSpanByTraceId.Scope())\n\t\t\tspansSlice.MoveAndAppendTo(scopeSpanByTraceId.Spans())\n\t\t\tscopeSpansSlice, ok := scopeSpansByTraceId[traceId]\n\t\t\tif !ok {\n\t\t\t\tscopeSpansSlice = ptrace.NewScopeSpansSlice()\n\t\t\t\tscopeSpansByTraceId[traceId] = scopeSpansSlice\n\t\t\t}\n\t\t\tscopeSpanByTraceId.MoveTo(scopeSpansSlice.AppendEmpty())\n\t\t}\n\t}\n\treturn scopeSpansByTraceId\n}\n\nfunc reshuffleSpans(spanSlice ptrace.SpanSlice) map[pcommon.TraceID]ptrace.SpanSlice {\n\tspansByTraceId := make(map[pcommon.TraceID]ptrace.SpanSlice)\n\tfor _, span := range spanSlice.All() {\n\t\tspansSlice, ok := spansByTraceId[span.TraceID()]\n\t\tif !ok {\n\t\t\tspansSlice = ptrace.NewSpanSlice()\n\t\t\tspansByTraceId[span.TraceID()] = spansSlice\n\t\t}\n\t\tspan.CopyTo(spansSlice.AppendEmpty())\n\t}\n\treturn spansByTraceId\n}\n\nfunc getServiceNameFromResource(resource pcommon.Resource) string {\n\tval, ok := resource.Attributes().Get(conventions.ServiceNameKey)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn val.Str()\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/memory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\tconventions \"github.com/jaegertracing/jaeger/internal/telemetry/otelsemconv\"\n\t\"github.com/jaegertracing/jaeger/internal/tenancy\"\n)\n\nfunc TestNewStore_DefaultConfig(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := loadInputTraces(t, 1)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\ttraceID1 := fromString(t, \"00000000000000010000000000000000\")\n\ttraceID2 := fromString(t, \"00000000000000020000000000000000\")\n\tgetTracesIter := store.GetTraces(context.Background(), tracestore.GetTraceParams{TraceID: traceID1}, tracestore.GetTraceParams{TraceID: traceID2})\n\tvar traces []ptrace.Traces\n\tfor gottd, err := range getTracesIter {\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, gottd, 1)\n\t\ttraces = append(traces, gottd[0])\n\t}\n\texpected := loadOutputTraces(t, 1)\n\ttestTraces(t, expected, traces[0])\n\texpected2 := loadOutputTraces(t, 2)\n\ttestTraces(t, expected2, traces[1])\n\toperations, err := store.GetOperations(context.Background(), tracestore.OperationQueryParams{ServiceName: \"service-x\"})\n\trequire.NoError(t, err)\n\texpectedOperations := []tracestore.Operation{\n\t\t{\n\t\t\tName:     \"test-general-conversion-2\",\n\t\t\tSpanKind: \"server\",\n\t\t},\n\t\t{\n\t\t\tName:     \"test-general-conversion-3\",\n\t\t\tSpanKind: \"client\",\n\t\t},\n\t\t{\n\t\t\tName:     \"test-general-conversion-4\",\n\t\t\tSpanKind: \"producer\",\n\t\t},\n\t\t{\n\t\t\tName:     \"test-general-conversion-5\",\n\t\t\tSpanKind: \"consumer\",\n\t\t},\n\t}\n\tsort.Slice(operations, func(i, j int) bool {\n\t\treturn operations[i].Name < operations[j].Name\n\t})\n\tassert.Equal(t, expectedOperations, operations)\n\texpectedServices := []string{\"service-x\"}\n\tservices, err := store.GetServices(context.Background())\n\trequire.NoError(t, err)\n\tassert.Equal(t, expectedServices, services)\n\tqueryAttrs := getQueryAttributes()\n\tfindTracesParams := tracestore.TraceQueryParams{\n\t\tServiceName:   \"service-x\",\n\t\tOperationName: \"test-general-conversion-2\",\n\t\tAttributes:    queryAttrs,\n\t\tSearchDepth:   5,\n\t}\n\tidsIter := store.FindTraceIDs(context.Background(), findTracesParams)\n\ti := 0\n\tfor foundTraceIds, err := range idsIter {\n\t\ti++\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, foundTraceIds, 1)\n\t\tassert.Equal(t, traceID1, foundTraceIds[0].TraceID)\n\t}\n\tassert.Equal(t, 1, i)\n\tgotIter := store.FindTraces(context.Background(), findTracesParams)\n\ti = 0\n\tfor foundTraces, err := range gotIter {\n\t\ti++\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, foundTraces, 1)\n\t\ttestTraces(t, expected, foundTraces[0])\n\t}\n\tassert.Equal(t, 1, i)\n}\n\nfunc getQueryAttributes() pcommon.Map {\n\tqueryAttrs := pcommon.NewMap()\n\tqueryAttrs.PutStr(\"peer.service\", \"service-y\")\n\tqueryAttrs.PutDouble(\"temperature\", 72.5)\n\tqueryAttrs.PutBool(errorAttribute, true)\n\tqueryAttrs.PutStr(\"event-x\", \"event-y\")\n\tqueryAttrs.PutStr(\"scope.attributes.2\", \"attribute-y\")\n\treturn queryAttrs\n}\n\nfunc TestFindTraces_WrongQuery(t *testing.T) {\n\twrongStringValue := \"wrongStringValue\"\n\tstartTime := time.Unix(0, int64(1485467191639875000))\n\tendTime := time.Unix(0, int64(1485467191639880000))\n\tduration := endTime.Sub(startTime)\n\ttests := []struct {\n\t\tname           string\n\t\tmodifyQueryFxn func(query *tracestore.TraceQueryParams)\n\t}{\n\t\t{\n\t\t\tname: \"wrong service-name\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.ServiceName = wrongStringValue\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong tag\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tattrs := pcommon.NewMap()\n\t\t\t\tattrs.PutStr(wrongStringValue, wrongStringValue)\n\t\t\t\tattrs.MoveTo(query.Attributes)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong operation name\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.OperationName = wrongStringValue\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong status code\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.Attributes.PutStr(errorAttribute, wrongStringValue)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong min start time\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.StartTimeMin = startTime.Add(1 * time.Hour)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong max start time\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.StartTimeMax = startTime.Add(-1 * time.Hour)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong min duration\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.DurationMin = duration + 1*time.Hour\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wrong max duration\",\n\t\t\tmodifyQueryFxn: func(query *tracestore.TraceQueryParams) {\n\t\t\t\tquery.DurationMax = duration - 1*time.Hour\n\t\t\t},\n\t\t},\n\t}\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := loadInputTraces(t, 1)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tquery := tracestore.TraceQueryParams{\n\t\t\t\tServiceName:   \"service-x\",\n\t\t\t\tOperationName: \"test-general-conversion-2\",\n\t\t\t\tAttributes:    getQueryAttributes(),\n\t\t\t\tSearchDepth:   10,\n\t\t\t}\n\t\t\ttt.modifyQueryFxn(&query)\n\t\t\tgotIter := store.FindTraces(context.Background(), query)\n\t\t\titerLength := 0\n\t\t\tfor _, err := range gotIter {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\titerLength++\n\t\t\t}\n\t\t\tassert.Equal(t, 0, iterLength)\n\t\t})\n\t}\n}\n\nfunc TestFindTracesAttributesMatching(t *testing.T) {\n\tstringVal := \"val\"\n\ttests := []struct {\n\t\tname       string\n\t\tattributes func(td ptrace.Traces) pcommon.Map\n\t}{\n\t\t{\n\t\t\tname: \"resource-attributes\",\n\t\t\tattributes: func(td ptrace.Traces) pcommon.Map {\n\t\t\t\treturn td.ResourceSpans().At(0).Resource().Attributes()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"scope-attributes\",\n\t\t\tattributes: func(td ptrace.Traces) pcommon.Map {\n\t\t\t\treturn td.ResourceSpans().At(0).ScopeSpans().At(0).Scope().Attributes()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"span-attributes\",\n\t\t\tattributes: func(td ptrace.Traces) pcommon.Map {\n\t\t\t\treturn td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Attributes()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"event-attributes\",\n\t\t\tattributes: func(td ptrace.Traces) pcommon.Map {\n\t\t\t\treturn td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Events().AppendEmpty().Attributes()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"link-attributes\",\n\t\t\tattributes: func(td ptrace.Traces) pcommon.Map {\n\t\t\t\treturn td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).Links().AppendEmpty().Attributes()\n\t\t\t},\n\t\t},\n\t}\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\tfor i, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttd := ptrace.NewTraces()\n\t\t\ttd.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetTraceID(fromString(t, fmt.Sprintf(\"000000000000000%d0000000000000000\", i+1)))\n\t\t\tattrs := tt.attributes(td)\n\t\t\tattrs.PutStr(tt.name, stringVal)\n\t\t\terr := store.WriteTraces(context.Background(), td)\n\t\t\trequire.NoError(t, err)\n\t\t\titer := store.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\t\t\tAttributes:  attrs,\n\t\t\t\tSearchDepth: 10,\n\t\t\t})\n\t\t\titerLength := 0\n\t\t\tfor traces, err := range iter {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\titerLength++\n\t\t\t\tassert.Len(t, traces, 1)\n\t\t\t\tassert.Equal(t, traces[0], td)\n\t\t\t}\n\t\t\tassert.Equal(t, 1, iterLength)\n\t\t})\n\t}\n}\n\nfunc TestFindTraces_MaxTraces(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\tfor i := 1; i < 9; i++ {\n\t\ttd := ptrace.NewTraces()\n\t\tspan := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\t\tspan.SetTraceID(fromString(t, fmt.Sprintf(\"000000000000000%d0000000000000000\", i)))\n\t\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\t\tspan.Attributes().PutBool(\"key\", true)\n\t\terr := store.WriteTraces(context.Background(), td)\n\t\trequire.NoError(t, err)\n\t}\n\tattrs := pcommon.NewMap()\n\tattrs.PutBool(\"key\", true)\n\tparams := tracestore.TraceQueryParams{\n\t\tSearchDepth: 5,\n\t\tAttributes:  attrs,\n\t}\n\tgotIter := store.FindTraces(context.Background(), params)\n\titerLength := 0\n\tfor traces, err := range gotIter {\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 1)\n\t\titerLength++\n\t}\n\tassert.Equal(t, 5, iterLength)\n\tnewIter := store.FindTraces(context.Background(), params)\n\titerLength = 0\n\tfor _, err := range newIter {\n\t\trequire.NoError(t, err)\n\t\titerLength++\n\t\tif iterLength > 3 {\n\t\t\tbreak\n\t\t}\n\t}\n\tassert.Equal(t, 4, iterLength)\n}\n\nfunc TestFindTraces_AttributesFoundInEvents(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := ptrace.NewTraces()\n\tspan := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.SetTraceID(fromString(t, \"00000000000000010000000000000000\"))\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan.Events().AppendEmpty().Attributes().PutBool(\"key\", true)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tqueryAttributes := pcommon.NewMap()\n\tqueryAttributes.PutBool(\"key\", true)\n\tparams := tracestore.TraceQueryParams{\n\t\tAttributes:  queryAttributes,\n\t\tSearchDepth: 10,\n\t}\n\tgotIter := store.FindTraces(context.Background(), params)\n\titerLength := 0\n\tfor traces, err := range gotIter {\n\t\titerLength++\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 1)\n\t\tassert.Equal(t, td, traces[0])\n\t}\n\tassert.Equal(t, 1, iterLength)\n}\n\nfunc TestFindTraces_ErrorStatusNotMatched(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := ptrace.NewTraces()\n\tspan := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.SetTraceID(fromString(t, \"00000000000000010000000000000000\"))\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan.Status().SetCode(ptrace.StatusCodeOk)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tqueryAttributes := pcommon.NewMap()\n\tqueryAttributes.PutBool(errorAttribute, true)\n\tparams := tracestore.TraceQueryParams{\n\t\tAttributes:  queryAttributes,\n\t\tSearchDepth: 10,\n\t}\n\tgotIter := store.FindTraces(context.Background(), params)\n\titerLength := 0\n\tfor _, err := range gotIter {\n\t\trequire.NoError(t, err)\n\t\titerLength++\n\t}\n\tassert.Equal(t, 0, iterLength)\n}\n\nfunc TestFindTraces_NegativeSearchDepthErr(t *testing.T) {\n\ttestInvalidSearchDepth(t, func(store *Store, params tracestore.TraceQueryParams) {\n\t\tgotIter := store.FindTraces(context.Background(), params)\n\t\titerLength := 0\n\t\tfor traces, err := range gotIter {\n\t\t\titerLength++\n\t\t\trequire.ErrorContains(t, err, errInvalidSearchDepth.Error())\n\t\t\tassert.Nil(t, traces)\n\t\t}\n\t\tassert.Equal(t, 1, iterLength)\n\t})\n}\n\nfunc TestFindTraceIds_NegativeSearchDepth(t *testing.T) {\n\ttestInvalidSearchDepth(t, func(store *Store, params tracestore.TraceQueryParams) {\n\t\tgotIter := store.FindTraceIDs(context.Background(), params)\n\t\titerLength := 0\n\t\tfor traces, err := range gotIter {\n\t\t\titerLength++\n\t\t\trequire.ErrorContains(t, err, errInvalidSearchDepth.Error())\n\t\t\tassert.Nil(t, traces)\n\t\t}\n\t\tassert.Equal(t, 1, iterLength)\n\t})\n}\n\nfunc testInvalidSearchDepth(t *testing.T, fxn func(store *Store, params tracestore.TraceQueryParams)) {\n\ttests := []struct {\n\t\tname        string\n\t\tsearchDepth int\n\t}{\n\t\t{\n\t\t\tname:        \"negative search depth\",\n\t\t\tsearchDepth: -1,\n\t\t},\n\t\t{\n\t\t\tname:        \"zero search depth\",\n\t\t\tsearchDepth: 0,\n\t\t},\n\t\t{\n\t\t\tname:        \"search depth greater than max traces\",\n\t\t\tsearchDepth: 11,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tstore, err := NewStore(Configuration{\n\t\t\t\tMaxTraces: 10,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tparams := tracestore.TraceQueryParams{\n\t\t\t\tSearchDepth: test.searchDepth,\n\t\t\t}\n\t\t\tfxn(store, params)\n\t\t})\n\t}\n}\n\nfunc TestFindTraces_StatusCode(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\ttraceId1 := fromString(t, \"00000000000000010000000000000000\")\n\ttraceId2 := fromString(t, \"00000000000000020000000000000000\")\n\trequire.NoError(t, err)\n\ttd := ptrace.NewTraces()\n\tspans := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans()\n\tspan1 := spans.AppendEmpty()\n\tspan2 := spans.AppendEmpty()\n\tspan1.SetTraceID(traceId1)\n\tspan1.Status().SetCode(ptrace.StatusCodeOk)\n\tspan2.SetTraceID(traceId2)\n\tspan2.Status().SetCode(ptrace.StatusCodeError)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tqueryAttributes := pcommon.NewMap()\n\tqueryAttributes.PutBool(errorAttribute, true)\n\titer1 := store.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes:  queryAttributes,\n\t\tSearchDepth: 10,\n\t})\n\titerLength := 0\n\tfor traces, err := range iter1 {\n\t\trequire.NoError(t, err)\n\t\titerLength++\n\t\tassert.Len(t, traces, 1)\n\t\tassert.Equal(t, traceId2, traces[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID())\n\t}\n\tassert.Equal(t, 1, iterLength)\n\titerLength = 0\n\tqueryAttributes.PutBool(errorAttribute, false)\n\titer2 := store.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tAttributes:  queryAttributes,\n\t\tSearchDepth: 10,\n\t})\n\tfor traces, err := range iter2 {\n\t\trequire.NoError(t, err)\n\t\titerLength++\n\t\tassert.Len(t, traces, 1)\n\t\tassert.Equal(t, traceId1, traces[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID())\n\t}\n\tassert.Equal(t, 1, iterLength)\n}\n\nfunc TestGetOperationsWithKind(t *testing.T) {\n\ttests := []struct {\n\t\tspanKind     ptrace.SpanKind\n\t\texpectedKind string\n\t}{\n\t\t{\n\t\t\tspanKind:     ptrace.SpanKindClient,\n\t\t\texpectedKind: \"client\",\n\t\t},\n\t\t{\n\t\t\tspanKind:     ptrace.SpanKindServer,\n\t\t\texpectedKind: \"server\",\n\t\t},\n\t\t{\n\t\t\tspanKind:     ptrace.SpanKindProducer,\n\t\t\texpectedKind: \"producer\",\n\t\t},\n\t\t{\n\t\t\tspanKind:     ptrace.SpanKindUnspecified,\n\t\t\texpectedKind: \"\",\n\t\t},\n\t\t{\n\t\t\tspanKind:     ptrace.SpanKindInternal,\n\t\t\texpectedKind: \"internal\",\n\t\t},\n\t\t{\n\t\t\tspanKind:     ptrace.SpanKindConsumer,\n\t\t\texpectedKind: \"consumer\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.spanKind.String(), func(t *testing.T) {\n\t\t\tstore, err := NewStore(Configuration{\n\t\t\t\tMaxTraces: 10,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\ttd := ptrace.NewTraces()\n\t\t\tresourceSpan := td.ResourceSpans().AppendEmpty()\n\t\t\tresourceSpan.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service-z\")\n\t\t\tspan := resourceSpan.ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\t\t\tspan.SetTraceID(fromString(t, \"00000000000000010000000000000000\"))\n\t\t\tspan.SetName(\"span\")\n\t\t\tspan.SetKind(test.spanKind)\n\t\t\terr = store.WriteTraces(context.Background(), td)\n\t\t\trequire.NoError(t, err)\n\t\t\toperations, err := store.GetOperations(context.Background(), tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"service-z\",\n\t\t\t\tSpanKind:    test.expectedKind,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Len(t, operations, 1)\n\t\t\tassert.Equal(t, operations[0].SpanKind, string(test.expectedKind))\n\t\t})\n\t}\n}\n\nfunc TestGetTraces_IterBreak(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := loadInputTraces(t, 1)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\ttraceID1 := fromString(t, \"00000000000000010000000000000000\")\n\ttraceID2 := fromString(t, \"00000000000000020000000000000000\")\n\titer := store.GetTraces(context.Background(), tracestore.GetTraceParams{TraceID: traceID1}, tracestore.GetTraceParams{TraceID: traceID2})\n\texpected := loadOutputTraces(t, 1)\n\titerLength := 1\n\tfor traces, err := range iter {\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 1)\n\t\tassert.Equal(t, expected, traces[0])\n\t\tbreak\n\t}\n\tassert.Equal(t, 1, iterLength)\n}\n\nfunc TestWriteTraces_WriteTwoBatches(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttraceId := fromString(t, \"00000000000000010000000000000000\")\n\ttd1 := ptrace.NewTraces()\n\ttd1.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetTraceID(traceId)\n\terr = store.WriteTraces(context.Background(), td1)\n\trequire.NoError(t, err)\n\ttd2 := ptrace.NewTraces()\n\ttd2.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetTraceID(traceId)\n\terr = store.WriteTraces(context.Background(), td2)\n\trequire.NoError(t, err)\n\ttenant := store.getTenant(tenancy.GetTenant(context.Background()))\n\trequire.NoError(t, err)\n\ttraceIndex := tenant.ids[traceId]\n\tassert.Equal(t, 2, tenant.traces[traceIndex].trace.ResourceSpans().Len())\n}\n\nfunc TestWriteTraces_WriteTraceWithTwoResourceSpans(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttraceId := fromString(t, \"00000000000000010000000000000000\")\n\ttd := ptrace.NewTraces()\n\tresourceSpans := td.ResourceSpans()\n\tscopeSpan1 := resourceSpans.AppendEmpty().ScopeSpans().AppendEmpty()\n\tscopeSpan1.Spans().AppendEmpty().SetTraceID(traceId)\n\tscopeSpan1.Spans().AppendEmpty().SetTraceID(traceId)\n\tscopeSpan2 := resourceSpans.AppendEmpty().ScopeSpans().AppendEmpty()\n\tscopeSpan2.Spans().AppendEmpty().SetTraceID(traceId)\n\tscopeSpan2.Spans().AppendEmpty().SetTraceID(traceId)\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\ttenant := store.getTenant(tenancy.GetTenant(context.Background()))\n\trequire.NoError(t, err)\n\ttraceIndex := tenant.ids[traceId]\n\t// All spans have same trace id, so output should be same as input (that is no reshuffling, effectively)\n\tassert.Equal(t, td, tenant.traces[traceIndex].trace)\n}\n\nfunc TestNewStore_TracesLimit(t *testing.T) {\n\tmaxTraces := 8\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: maxTraces,\n\t})\n\trequire.NoError(t, err)\n\twriteTenTraces(t, store)\n\ttenant := store.getTenant(tenancy.GetTenant(context.Background()))\n\trequire.NoError(t, err)\n\tassert.Len(t, tenant.traces, maxTraces)\n\tassert.Len(t, tenant.ids, maxTraces)\n}\n\nfunc TestNewStore_ReverseChronologicalOrder(t *testing.T) {\n\tmaxTraces := 8\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: maxTraces,\n\t})\n\trequire.NoError(t, err)\n\twriteTenTraces(t, store)\n\titer := store.FindTraces(context.Background(), tracestore.TraceQueryParams{\n\t\tSearchDepth: 5,\n\t\tAttributes:  pcommon.NewMap(),\n\t})\n\t// This test whether the traces are fetched in Reverse Chronological Order\n\titerLength := 0\n\tfor traces, err := range iter {\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, traces, 1)\n\t\tactualTraceId := traces[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\t\tassert.Equal(t, fromString(t, fmt.Sprintf(\"000000000000000%d0000000000000000\", 9-iterLength)), actualTraceId)\n\t\titerLength++\n\t}\n\tassert.Equal(t, 5, iterLength)\n}\n\nfunc TestInvalidMaxTracesErr(t *testing.T) {\n\tstore, err := NewStore(Configuration{})\n\trequire.ErrorContains(t, err, errInvalidMaxTraces.Error())\n\tassert.Nil(t, store)\n}\n\nfunc TestGetDependencies(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttraceId := fromString(t, \"00000000000000010000000000000000\")\n\ttd := ptrace.NewTraces()\n\tresourceSpans := td.ResourceSpans().AppendEmpty()\n\tresourceSpans.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service-x\")\n\tspan1StartTime := time.Now()\n\tspan1 := resourceSpans.ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan1.SetTraceID(traceId)\n\tspan1.SetSpanID(spanIdFromString(t, \"0000000000000001\"))\n\tspan1.SetParentSpanID(spanIdFromString(t, \"0000000000000003\"))\n\tspan1.SetStartTimestamp(pcommon.NewTimestampFromTime(span1StartTime))\n\tspan1.SetEndTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(1 * time.Second)))\n\tspan2 := resourceSpans.ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan2.SetTraceID(traceId)\n\tspan2.SetSpanID(spanIdFromString(t, \"0000000000000002\"))\n\tspan2.SetParentSpanID(spanIdFromString(t, \"0000000000000003\"))\n\tspan2.SetStartTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(1 * time.Second)))\n\tspan2.SetEndTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(2 * time.Second)))\n\tnewResourceSpan := td.ResourceSpans().AppendEmpty()\n\tnewResourceSpan.Resource().Attributes().PutStr(string(conventions.ServiceNameKey), \"service-y\")\n\tspan3 := newResourceSpan.ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan3.SetTraceID(traceId)\n\tspan3.SetSpanID(spanIdFromString(t, \"0000000000000003\"))\n\tspan3.SetStartTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(-2 * time.Second)))\n\tspan3.SetEndTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(3 * time.Second)))\n\tspan3.SetParentSpanID(spanIdFromString(t, \"0000000000000004\"))\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tdeps, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: span1StartTime.Add(-4 * time.Second),\n\t\tEndTime:   span1StartTime.Add(5 * time.Second),\n\t})\n\trequire.NoError(t, err)\n\tassert.Len(t, deps, 1)\n\tassert.Equal(t, model.DependencyLink{\n\t\tParent:    \"service-y\",\n\t\tChild:     \"service-x\",\n\t\tCallCount: 2,\n\t}, deps[0])\n\ttd2 := ptrace.NewTraces()\n\tresourceSpan2 := td2.ResourceSpans().AppendEmpty()\n\tresourceSpan2.Resource().Attributes().PutStr(string(conventions.ServiceNameKey), \"service-z\")\n\tspan4 := resourceSpan2.ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan4.SetTraceID(traceId)\n\tspan4.SetSpanID(spanIdFromString(t, \"0000000000000004\"))\n\tspan4.SetStartTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(-4 * time.Second)))\n\tspan4.SetEndTimestamp(pcommon.NewTimestampFromTime(span1StartTime.Add(5 * time.Second)))\n\terr = store.WriteTraces(context.Background(), td2)\n\trequire.NoError(t, err)\n\tnewDeps, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: span1StartTime.Add(-5 * time.Second),\n\t\tEndTime:   span1StartTime.Add(6 * time.Second),\n\t})\n\trequire.NoError(t, err)\n\tassert.Len(t, newDeps, 2)\n\tnewDeps2, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: span1StartTime.Add(-5 * time.Second),\n\t})\n\trequire.NoError(t, err)\n\tassert.Len(t, newDeps2, 2)\n\temptyDeps, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: span1StartTime.Add(-4 * time.Second),\n\t\tEndTime:   span1StartTime.Add(5 * time.Second),\n\t})\n\trequire.NoError(t, err)\n\tassert.Empty(t, emptyDeps)\n}\n\nfunc TestGetDependencies_Err(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\tstartTime := time.Now()\n\tdeps, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: startTime,\n\t\tEndTime:   startTime.Add(-1 * time.Second),\n\t})\n\trequire.ErrorContains(t, err, \"end time must be greater than start time\")\n\tassert.Nil(t, deps)\n\tdeps, err = store.GetDependencies(context.Background(), depstore.QueryParameters{})\n\trequire.ErrorContains(t, err, \"start time is required\")\n\tassert.Nil(t, deps)\n}\n\nfunc TestGetDependencies_EmptyParentSpanId(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := ptrace.NewTraces()\n\tspan := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.SetTraceID(fromString(t, \"00000000000000010000000000000000\"))\n\tstartTime := time.Now()\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(startTime))\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(startTime.Add(1 * time.Second)))\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tdeps, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: startTime.Add(-1 * time.Second),\n\t\tEndTime:   startTime.Add(2 * time.Second),\n\t})\n\trequire.NoError(t, err)\n\tassert.Empty(t, deps)\n}\n\nfunc TestGetDependencies_WrongSpanId(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 10,\n\t})\n\trequire.NoError(t, err)\n\ttd := ptrace.NewTraces()\n\tspan := td.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty()\n\tspan.SetTraceID(fromString(t, \"00000000000000010000000000000000\"))\n\tstartTime := time.Now()\n\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(startTime))\n\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(startTime.Add(1 * time.Second)))\n\tspan.SetSpanID(spanIdFromString(t, \"0000000000000002\"))\n\terr = store.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\tdeps, err := store.GetDependencies(context.Background(), depstore.QueryParameters{\n\t\tStartTime: startTime.Add(-1 * time.Second),\n\t\tEndTime:   startTime.Add(2 * time.Second),\n\t})\n\trequire.NoError(t, err)\n\tassert.Empty(t, deps)\n}\n\nfunc writeTenTraces(t *testing.T, store *Store) {\n\tfor i := 1; i < 10; i++ {\n\t\ttraceID := fromString(t, fmt.Sprintf(\"000000000000000%d0000000000000000\", i))\n\t\ttraces := ptrace.NewTraces()\n\t\ttraces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetTraceID(traceID)\n\t\terr := store.WriteTraces(context.Background(), traces)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc fromString(t *testing.T, dbTraceId string) pcommon.TraceID {\n\tvar traceId [16]byte\n\ttraceBytes, err := hex.DecodeString(dbTraceId)\n\trequire.NoError(t, err)\n\tcopy(traceId[:], traceBytes)\n\treturn traceId\n}\n\nfunc spanIdFromString(t *testing.T, dbTraceId string) pcommon.SpanID {\n\tvar spanId [8]byte\n\tspanIdBytes, err := hex.DecodeString(dbTraceId)\n\trequire.NoError(t, err)\n\tcopy(spanId[:], spanIdBytes)\n\treturn spanId\n}\n\nfunc testTraces(t *testing.T, expectedTraces ptrace.Traces, actualTraces ptrace.Traces) {\n\tif !assert.Equal(t, expectedTraces, actualTraces) {\n\t\tmarshaller := ptrace.JSONMarshaler{}\n\t\tactualTd, err := marshaller.MarshalTraces(actualTraces)\n\t\trequire.NoError(t, err)\n\t\twriteActualData(t, \"traces\", actualTd)\n\t}\n}\n\nfunc writeActualData(t *testing.T, name string, data []byte) {\n\tvar prettyJson bytes.Buffer\n\terr := json.Indent(&prettyJson, data, \"\", \"  \")\n\trequire.NoError(t, err)\n\tpath := \"fixtures/actual_\" + name + \".json\"\n\terr = os.WriteFile(path, prettyJson.Bytes(), 0o644)\n\trequire.NoError(t, err)\n\tt.Log(\"Saved the actual \" + name + \" to \" + path)\n}\n\nfunc loadInputTraces(t *testing.T, i int) ptrace.Traces {\n\treturn loadTraces(t, fmt.Sprintf(\"fixtures/otel_traces_%02d.json\", i))\n}\n\nfunc loadOutputTraces(t *testing.T, i int) ptrace.Traces {\n\treturn loadTraces(t, fmt.Sprintf(\"fixtures/db_traces_%02d.json\", i))\n}\n\nfunc loadTraces(t *testing.T, name string) ptrace.Traces {\n\tunmarshller := ptrace.JSONUnmarshaler{}\n\tdata, err := os.ReadFile(name)\n\trequire.NoError(t, err)\n\ttd, err := unmarshller.UnmarshalTraces(data)\n\trequire.NoError(t, err)\n\treturn td\n}\n\nfunc TestFindTraces_OTLPFields(t *testing.T) {\n\tstore, err := NewStore(Configuration{\n\t\tMaxTraces: 100,\n\t})\n\trequire.NoError(t, err)\n\n\ttraceID1 := fromString(t, \"00000000000000010000000000000000\")\n\ttraceID2 := fromString(t, \"00000000000000020000000000000000\")\n\ttraceID3 := fromString(t, \"00000000000000030000000000000000\")\n\ttraceID4 := fromString(t, \"00000000000000040000000000000000\")\n\ttraceID5 := fromString(t, \"00000000000000050000000000000000\")\n\n\t// Trace 1: ERROR status, SERVER kind, scope \"my-scope\" v1.0.0, resource.deployment.environment=production\n\ttd1 := ptrace.NewTraces()\n\trs1 := td1.ResourceSpans().AppendEmpty()\n\trs1.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service1\")\n\trs1.Resource().Attributes().PutStr(\"deployment.environment\", \"production\")\n\tss1 := rs1.ScopeSpans().AppendEmpty()\n\tss1.Scope().SetName(\"my-scope\")\n\tss1.Scope().SetVersion(\"1.0.0\")\n\tspan1 := ss1.Spans().AppendEmpty()\n\tspan1.SetTraceID(traceID1)\n\tspan1.SetSpanID(spanIdFromString(t, \"0000000000000001\"))\n\tspan1.SetName(\"operation1\")\n\tspan1.SetKind(ptrace.SpanKindServer)\n\tspan1.Status().SetCode(ptrace.StatusCodeError)\n\tspan1.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan1.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(time.Second)))\n\n\t// Trace 2: OK status, CLIENT kind, scope \"other-scope\" v2.0.0, resource.deployment.environment=staging\n\ttd2 := ptrace.NewTraces()\n\trs2 := td2.ResourceSpans().AppendEmpty()\n\trs2.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service2\")\n\trs2.Resource().Attributes().PutStr(\"deployment.environment\", \"staging\")\n\tss2 := rs2.ScopeSpans().AppendEmpty()\n\tss2.Scope().SetName(\"other-scope\")\n\tss2.Scope().SetVersion(\"2.0.0\")\n\tspan2 := ss2.Spans().AppendEmpty()\n\tspan2.SetTraceID(traceID2)\n\tspan2.SetSpanID(spanIdFromString(t, \"0000000000000002\"))\n\tspan2.SetName(\"operation2\")\n\tspan2.SetKind(ptrace.SpanKindClient)\n\tspan2.Status().SetCode(ptrace.StatusCodeOk)\n\tspan2.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan2.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(time.Second)))\n\n\t// Trace 3: PRODUCER kind with UNSET status\n\ttd3 := ptrace.NewTraces()\n\trs3 := td3.ResourceSpans().AppendEmpty()\n\trs3.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service3\")\n\tss3 := rs3.ScopeSpans().AppendEmpty()\n\tspan3 := ss3.Spans().AppendEmpty()\n\tspan3.SetTraceID(traceID3)\n\tspan3.SetSpanID(spanIdFromString(t, \"0000000000000003\"))\n\tspan3.SetName(\"operation3\")\n\tspan3.SetKind(ptrace.SpanKindProducer)\n\tspan3.Status().SetCode(ptrace.StatusCodeUnset)\n\tspan3.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan3.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(time.Second)))\n\n\t// Trace 4: CONSUMER kind with UNSET status\n\ttd4 := ptrace.NewTraces()\n\trs4 := td4.ResourceSpans().AppendEmpty()\n\trs4.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service4\")\n\tss4 := rs4.ScopeSpans().AppendEmpty()\n\tspan4 := ss4.Spans().AppendEmpty()\n\tspan4.SetTraceID(traceID4)\n\tspan4.SetSpanID(spanIdFromString(t, \"0000000000000004\"))\n\tspan4.SetName(\"operation4\")\n\tspan4.SetKind(ptrace.SpanKindConsumer)\n\tspan4.Status().SetCode(ptrace.StatusCodeUnset)\n\tspan4.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan4.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(time.Second)))\n\n\t// Trace 5: INTERNAL kind with UNSET status\n\ttd5 := ptrace.NewTraces()\n\trs5 := td5.ResourceSpans().AppendEmpty()\n\trs5.Resource().Attributes().PutStr(conventions.ServiceNameKey, \"service5\")\n\tss5 := rs5.ScopeSpans().AppendEmpty()\n\tspan5 := ss5.Spans().AppendEmpty()\n\tspan5.SetTraceID(traceID5)\n\tspan5.SetSpanID(spanIdFromString(t, \"0000000000000005\"))\n\tspan5.SetName(\"operation5\")\n\tspan5.SetKind(ptrace.SpanKindInternal)\n\tspan5.Status().SetCode(ptrace.StatusCodeUnset)\n\tspan5.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now()))\n\tspan5.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(time.Second)))\n\n\t// Write traces\n\terr = store.WriteTraces(context.Background(), td1)\n\trequire.NoError(t, err)\n\terr = store.WriteTraces(context.Background(), td2)\n\trequire.NoError(t, err)\n\terr = store.WriteTraces(context.Background(), td3)\n\trequire.NoError(t, err)\n\terr = store.WriteTraces(context.Background(), td4)\n\trequire.NoError(t, err)\n\terr = store.WriteTraces(context.Background(), td5)\n\trequire.NoError(t, err)\n\n\ttests := []struct {\n\t\tname           string\n\t\tqueryAttrs     map[string]string\n\t\texpectedTraces int\n\t\texpectedIDs    []pcommon.TraceID\n\t}{\n\t\t{\n\t\t\tname:           \"Filter by span.status=ERROR\",\n\t\t\tqueryAttrs:     map[string]string{\"span.status\": \"ERROR\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.status=OK\",\n\t\t\tqueryAttrs:     map[string]string{\"span.status\": \"OK\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID2},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.status=UNSET\",\n\t\t\tqueryAttrs:     map[string]string{\"span.status\": \"UNSET\"},\n\t\t\texpectedTraces: 3,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID5, traceID4, traceID3},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=SERVER\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"SERVER\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=CLIENT\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"CLIENT\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID2},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=PRODUCER\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"PRODUCER\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID3},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=CONSUMER\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"CONSUMER\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID4},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=INTERNAL\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"INTERNAL\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID5},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=UNSPECIFIED (no match)\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"UNSPECIFIED\"},\n\t\t\texpectedTraces: 0,\n\t\t\texpectedIDs:    []pcommon.TraceID{},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by span.kind=INVALID (default/unknown)\",\n\t\t\tqueryAttrs:     map[string]string{\"span.kind\": \"INVALID\"},\n\t\t\texpectedTraces: 0,\n\t\t\texpectedIDs:    []pcommon.TraceID{},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by scope.name=my-scope\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.name\": \"my-scope\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by scope.name=other-scope\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.name\": \"other-scope\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID2},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by scope.name (no match)\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.name\": \"nonexistent\"},\n\t\t\texpectedTraces: 0,\n\t\t\texpectedIDs:    []pcommon.TraceID{},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by scope.version=1.0.0\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.version\": \"1.0.0\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by scope.version=2.0.0\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.version\": \"2.0.0\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID2},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by scope.version (no match)\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.version\": \"99.0.0\"},\n\t\t\texpectedTraces: 0,\n\t\t\texpectedIDs:    []pcommon.TraceID{},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by resource.deployment.environment=production\",\n\t\t\tqueryAttrs:     map[string]string{\"resource.deployment.environment\": \"production\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by resource.deployment.environment=staging\",\n\t\t\tqueryAttrs:     map[string]string{\"resource.deployment.environment\": \"staging\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID2},\n\t\t},\n\t\t{\n\t\t\tname:           \"Filter by resource.deployment.environment (no match)\",\n\t\t\tqueryAttrs:     map[string]string{\"resource.deployment.environment\": \"development\"},\n\t\t\texpectedTraces: 0,\n\t\t\texpectedIDs:    []pcommon.TraceID{},\n\t\t},\n\t\t{\n\t\t\tname:           \"Combined: span.status=ERROR AND span.kind=SERVER\",\n\t\t\tqueryAttrs:     map[string]string{\"span.status\": \"ERROR\", \"span.kind\": \"SERVER\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"No match: span.status=ERROR AND span.kind=CLIENT\",\n\t\t\tqueryAttrs:     map[string]string{\"span.status\": \"ERROR\", \"span.kind\": \"CLIENT\"},\n\t\t\texpectedTraces: 0,\n\t\t\texpectedIDs:    []pcommon.TraceID{},\n\t\t},\n\t\t{\n\t\t\tname:           \"Combined: scope.name AND scope.version\",\n\t\t\tqueryAttrs:     map[string]string{\"scope.name\": \"my-scope\", \"scope.version\": \"1.0.0\"},\n\t\t\texpectedTraces: 1,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID1},\n\t\t},\n\t\t{\n\t\t\tname:           \"No OTLP filters (backward compatibility)\",\n\t\t\tqueryAttrs:     map[string]string{},\n\t\t\texpectedTraces: 5,\n\t\t\texpectedIDs:    []pcommon.TraceID{traceID5, traceID4, traceID3, traceID2, traceID1}, // Reverse chronological\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tattrs := pcommon.NewMap()\n\t\t\tfor k, v := range tt.queryAttrs {\n\t\t\t\tattrs.PutStr(k, v)\n\t\t\t}\n\n\t\t\tquery := tracestore.TraceQueryParams{\n\t\t\t\tAttributes:  attrs,\n\t\t\t\tSearchDepth: 100,\n\t\t\t}\n\n\t\t\titer := store.FindTraces(context.Background(), query)\n\t\t\tvar foundTraces []ptrace.Traces\n\t\t\tfor traces, err := range iter {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tfoundTraces = append(foundTraces, traces...)\n\t\t\t}\n\n\t\t\tassert.Len(t, foundTraces, tt.expectedTraces,\n\t\t\t\t\"query: %v\", tt.queryAttrs)\n\n\t\t\tif tt.expectedTraces > 0 {\n\t\t\t\tfor i, expectedID := range tt.expectedIDs {\n\t\t\t\t\tactualID := foundTraces[i].ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\t\t\t\t\tassert.Equal(t, expectedID, actualID)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/package_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/sampling.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\n// SamplingStore is an in-memory store for sampling data\ntype SamplingStore struct {\n\tmu                  sync.RWMutex\n\tthroughputs         []*storedThroughput\n\tprobabilitiesAndQPS *storedServiceOperationProbabilitiesAndQPS\n\tmaxBuckets          int\n}\n\ntype storedThroughput struct {\n\tthroughput []*model.Throughput\n\ttime       time.Time\n}\n\ntype storedServiceOperationProbabilitiesAndQPS struct {\n\thostname      string\n\tprobabilities model.ServiceOperationProbabilities\n\tqps           model.ServiceOperationQPS\n\ttime          time.Time\n}\n\n// NewSamplingStore creates an in-memory sampling store.\nfunc NewSamplingStore(maxBuckets int) *SamplingStore {\n\treturn &SamplingStore{maxBuckets: maxBuckets}\n}\n\n// InsertThroughput implements samplingstore.Store#InsertThroughput.\nfunc (ss *SamplingStore) InsertThroughput(throughput []*model.Throughput) error {\n\tss.mu.Lock()\n\tdefer ss.mu.Unlock()\n\tnow := time.Now()\n\tss.preprendThroughput(&storedThroughput{throughput, now})\n\treturn nil\n}\n\n// GetThroughput implements samplingstore.Store#GetThroughput.\nfunc (ss *SamplingStore) GetThroughput(start, end time.Time) ([]*model.Throughput, error) {\n\tss.mu.Lock()\n\tdefer ss.mu.Unlock()\n\tvar retSlice []*model.Throughput\n\tfor _, t := range ss.throughputs {\n\t\tif t.time.After(start) && (t.time.Before(end) || t.time.Equal(end)) {\n\t\t\tretSlice = append(retSlice, t.throughput...)\n\t\t}\n\t}\n\treturn retSlice, nil\n}\n\n// InsertProbabilitiesAndQPS implements samplingstore.Store#InsertProbabilitiesAndQPS.\nfunc (ss *SamplingStore) InsertProbabilitiesAndQPS(\n\thostname string,\n\tprobabilities model.ServiceOperationProbabilities,\n\tqps model.ServiceOperationQPS,\n) error {\n\tss.mu.Lock()\n\tdefer ss.mu.Unlock()\n\tss.probabilitiesAndQPS = &storedServiceOperationProbabilitiesAndQPS{hostname, probabilities, qps, time.Now()}\n\treturn nil\n}\n\n// GetLatestProbabilities implements samplingstore.Store#GetLatestProbabilities.\nfunc (ss *SamplingStore) GetLatestProbabilities() (model.ServiceOperationProbabilities, error) {\n\tss.mu.Lock()\n\tdefer ss.mu.Unlock()\n\tif ss.probabilitiesAndQPS != nil {\n\t\treturn ss.probabilitiesAndQPS.probabilities, nil\n\t}\n\treturn model.ServiceOperationProbabilities{}, nil\n}\n\nfunc (ss *SamplingStore) preprendThroughput(throughput *storedThroughput) {\n\tss.throughputs = append([]*storedThroughput{throughput}, ss.throughputs...)\n\tif len(ss.throughputs) > ss.maxBuckets {\n\t\tss.throughputs = ss.throughputs[0:ss.maxBuckets]\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/sampling_test.go",
    "content": "// Copyright (c) 2021 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/samplingstore/model\"\n)\n\nfunc withPopulatedSamplingStore(f func(samplingStore *SamplingStore)) {\n\tnow := time.Now()\n\tmillisAfter := now.Add(time.Millisecond * time.Duration(100))\n\tsecondsAfter := now.Add(time.Second * time.Duration(2))\n\tthroughputs := []*storedThroughput{\n\t\t{[]*model.Throughput{{Service: \"svc-1\", Operation: \"op-1\", Count: 1}}, now},\n\t\t{[]*model.Throughput{{Service: \"svc-1\", Operation: \"op-2\", Count: 1}}, millisAfter},\n\t\t{[]*model.Throughput{{Service: \"svc-2\", Operation: \"op-3\", Count: 1}}, secondsAfter},\n\t}\n\tpQPS := &storedServiceOperationProbabilitiesAndQPS{\n\t\thostname: \"guntur38ab8928\", probabilities: model.ServiceOperationProbabilities{\"svc-1\": {\"op-1\": 0.01}}, qps: model.ServiceOperationQPS{\"svc-1\": {\"op-1\": 10.0}}, time: now,\n\t}\n\tsamplingStore := &SamplingStore{throughputs: throughputs, probabilitiesAndQPS: pQPS}\n\tf(samplingStore)\n}\n\nfunc withMemorySamplingStore(f func(samplingStore *SamplingStore)) {\n\tf(NewSamplingStore(5))\n}\n\nfunc TestInsertThroughtput(t *testing.T) {\n\twithMemorySamplingStore(func(samplingStore *SamplingStore) {\n\t\tstart := time.Now()\n\t\tthroughputs := []*model.Throughput{\n\t\t\t{Service: \"my-svc\", Operation: \"op\"},\n\t\t\t{Service: \"our-svc\", Operation: \"op2\"},\n\t\t}\n\t\trequire.NoError(t, samplingStore.InsertThroughput(throughputs))\n\t\tret, _ := samplingStore.GetThroughput(start, start.Add(time.Second*time.Duration(1)))\n\t\tassert.Len(t, ret, 2)\n\n\t\tfor i := range 10 {\n\t\t\tin := []*model.Throughput{\n\t\t\t\t{Service: fmt.Sprint(\"svc-\", i), Operation: fmt.Sprint(\"op-\", i)},\n\t\t\t}\n\t\t\tsamplingStore.InsertThroughput(in)\n\t\t}\n\t\tassert.Len(t, samplingStore.throughputs, 5)\n\t})\n}\n\nfunc TestGetThroughput(t *testing.T) {\n\twithPopulatedSamplingStore(func(samplingStore *SamplingStore) {\n\t\tstart := time.Now()\n\t\tret, err := samplingStore.GetThroughput(start, start.Add(time.Second*time.Duration(1)))\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, ret, 1)\n\t\tret1, _ := samplingStore.GetThroughput(start, start)\n\t\tassert.Empty(t, ret1)\n\t\tret2, _ := samplingStore.GetThroughput(start, start.Add(time.Hour*time.Duration(1)))\n\t\tassert.Len(t, ret2, 2)\n\t})\n}\n\nfunc TestInsertProbabilitiesAndQPS(t *testing.T) {\n\twithMemorySamplingStore(func(samplingStore *SamplingStore) {\n\t\trequire.NoError(t, samplingStore.InsertProbabilitiesAndQPS(\"dell11eg843d\", model.ServiceOperationProbabilities{\"new-srv\": {\"op\": 0.1}}, model.ServiceOperationQPS{\"new-srv\": {\"op\": 4}}))\n\t\tassert.NotEmpty(t, 1, samplingStore.probabilitiesAndQPS)\n\t\t// Only latest one is kept in memory\n\t\trequire.NoError(t, samplingStore.InsertProbabilitiesAndQPS(\"lncol73\", model.ServiceOperationProbabilities{\"my-app\": {\"hello\": 0.3}}, model.ServiceOperationQPS{\"new-srv\": {\"op\": 7}}))\n\t\tassert.InDelta(t, 0.3, samplingStore.probabilitiesAndQPS.probabilities[\"my-app\"][\"hello\"], 0.01)\n\t})\n}\n\nfunc TestGetLatestProbability(t *testing.T) {\n\twithMemorySamplingStore(func(samplingStore *SamplingStore) {\n\t\t// No priod data\n\t\tret, err := samplingStore.GetLatestProbabilities()\n\t\trequire.NoError(t, err)\n\t\tassert.Empty(t, ret)\n\t})\n\n\twithPopulatedSamplingStore(func(samplingStore *SamplingStore) {\n\t\t// With some pregenerated data\n\t\tret, err := samplingStore.GetLatestProbabilities()\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, model.ServiceOperationProbabilities{\"svc-1\": {\"op-1\": 0.01}}, ret)\n\t\trequire.NoError(t, samplingStore.InsertProbabilitiesAndQPS(\"utfhyolf\", model.ServiceOperationProbabilities{\"another-service\": {\"hello\": 0.009}}, model.ServiceOperationQPS{\"new-srv\": {\"op\": 5}}))\n\t\tret, _ = samplingStore.GetLatestProbabilities()\n\t\tassert.NotEqual(t, model.ServiceOperationProbabilities{\"svc-1\": {\"op-1\": 0.01}}, ret)\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v2/memory/tenant.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage memory\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar errInvalidMaxTraces = errors.New(\"max traces must be greater than zero\")\n\n// Tenant is an in-memory store of traces for a single tenant\ntype Tenant struct {\n\tmu     sync.RWMutex\n\tconfig *Configuration\n\n\tids        map[pcommon.TraceID]int // maps trace id to index in traces[]\n\ttraces     []traceAndId            // ring buffer to store traces\n\tmostRecent int                     // position in traces[] of the most recently added trace\n\n\tservices   map[string]struct{}\n\toperations map[string]map[tracestore.Operation]struct{}\n}\n\ntype traceAndId struct {\n\tid        pcommon.TraceID\n\ttrace     ptrace.Traces\n\tstartTime time.Time\n\tendTime   time.Time\n}\n\nfunc (t traceAndId) traceIsBetweenStartAndEnd(startTime time.Time, endTime time.Time) bool {\n\tif endTime.IsZero() {\n\t\treturn t.startTime.After(startTime)\n\t}\n\treturn t.startTime.After(startTime) && t.endTime.Before(endTime)\n}\n\nfunc newTenant(cfg *Configuration) *Tenant {\n\treturn &Tenant{\n\t\tconfig:     cfg,\n\t\tids:        make(map[pcommon.TraceID]int),\n\t\ttraces:     make([]traceAndId, cfg.MaxTraces),\n\t\tmostRecent: -1,\n\t\tservices:   map[string]struct{}{},\n\t\toperations: map[string]map[tracestore.Operation]struct{}{},\n\t}\n}\n\nfunc (t *Tenant) storeTraces(tracesById map[pcommon.TraceID]ptrace.ResourceSpansSlice) {\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\tfor traceId, sameTraceIDResourceSpan := range tracesById {\n\t\tvar startTime time.Time\n\t\tvar endTime time.Time\n\t\tfor _, resourceSpan := range sameTraceIDResourceSpan.All() {\n\t\t\tserviceName := getServiceNameFromResource(resourceSpan.Resource())\n\t\t\tif serviceName != \"\" {\n\t\t\t\tt.services[serviceName] = struct{}{}\n\t\t\t}\n\t\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\t\tif serviceName != \"\" {\n\t\t\t\t\t\toperation := tracestore.Operation{\n\t\t\t\t\t\t\tName:     span.Name(),\n\t\t\t\t\t\t\tSpanKind: fromOTELSpanKind(span.Kind()),\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif _, ok := t.operations[serviceName]; !ok {\n\t\t\t\t\t\t\tt.operations[serviceName] = make(map[tracestore.Operation]struct{})\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.operations[serviceName][operation] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t\tif startTime.IsZero() || span.StartTimestamp().AsTime().Before(startTime) {\n\t\t\t\t\t\tstartTime = span.StartTimestamp().AsTime()\n\t\t\t\t\t}\n\t\t\t\t\tif endTime.IsZero() || span.EndTimestamp().AsTime().After(endTime) {\n\t\t\t\t\t\tendTime = span.EndTimestamp().AsTime()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif index, ok := t.ids[traceId]; ok {\n\t\t\tsameTraceIDResourceSpan.MoveAndAppendTo(t.traces[index].trace.ResourceSpans())\n\t\t\tif startTime.Before(t.traces[index].startTime) {\n\t\t\t\tt.traces[index].startTime = startTime\n\t\t\t}\n\t\t\tif endTime.After(t.traces[index].endTime) {\n\t\t\t\tt.traces[index].endTime = endTime\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\ttraces := ptrace.NewTraces()\n\t\tsameTraceIDResourceSpan.MoveAndAppendTo(traces.ResourceSpans())\n\t\tt.mostRecent = (t.mostRecent + 1) % t.config.MaxTraces\n\t\t// if there is already a trace in lastEvicted position, remove its ID from ids map\n\t\tif !t.traces[t.mostRecent].id.IsEmpty() {\n\t\t\tdelete(t.ids, t.traces[t.mostRecent].id)\n\t\t}\n\t\t// update the ring with the trace id\n\t\tt.ids[traceId] = t.mostRecent\n\t\tt.traces[t.mostRecent] = traceAndId{\n\t\t\tid:        traceId,\n\t\t\ttrace:     traces,\n\t\t\tstartTime: startTime,\n\t\t\tendTime:   endTime,\n\t\t}\n\t}\n}\n\nfunc (t *Tenant) findTraceAndIds(query tracestore.TraceQueryParams) ([]traceAndId, error) {\n\tif query.SearchDepth <= 0 || query.SearchDepth > t.config.MaxTraces {\n\t\treturn nil, errInvalidSearchDepth\n\t}\n\tt.mu.RLock()\n\tdefer t.mu.RUnlock()\n\ttraceAndIds := make([]traceAndId, 0, query.SearchDepth)\n\tn := len(t.traces)\n\tfor i := range t.traces {\n\t\tif len(traceAndIds) == query.SearchDepth {\n\t\t\tbreak\n\t\t}\n\t\tindex := (t.mostRecent - i + n) % n\n\t\ttraceById := t.traces[index]\n\t\tif traceById.id.IsEmpty() {\n\t\t\t// Finding an empty ID means we reached a gap in the ring buffer\n\t\t\t// that has not yet been filled with traces.\n\t\t\tbreak\n\t\t}\n\t\tif validTrace(traceById.trace, query) {\n\t\t\ttraceAndIds = append(traceAndIds, traceById)\n\t\t}\n\t}\n\treturn traceAndIds, nil\n}\n\nfunc (t *Tenant) getTraces(traceIds ...tracestore.GetTraceParams) []ptrace.Traces {\n\tt.mu.RLock()\n\tdefer t.mu.RUnlock()\n\ttraces := make([]ptrace.Traces, 0)\n\tfor i := range traceIds {\n\t\tindex, ok := t.ids[traceIds[i].TraceID]\n\t\tif ok {\n\t\t\ttraces = append(traces, t.traces[index].trace)\n\t\t}\n\t}\n\treturn traces\n}\n\nfunc (t *Tenant) getDependencies(query depstore.QueryParameters) ([]model.DependencyLink, error) {\n\tif query.StartTime.IsZero() {\n\t\treturn nil, errors.New(\"start time is required\")\n\t}\n\tif !query.EndTime.IsZero() && query.EndTime.Before(query.StartTime) {\n\t\treturn nil, errors.New(\"end time must be greater than start time\")\n\t}\n\tt.mu.RLock()\n\tdefer t.mu.RUnlock()\n\tdeps := map[string]*model.DependencyLink{}\n\tfor _, index := range t.ids {\n\t\ttraceWithTime := t.traces[index]\n\t\tif !traceWithTime.traceIsBetweenStartAndEnd(query.StartTime, query.EndTime) {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, resourceSpan := range traceWithTime.trace.ResourceSpans().All() {\n\t\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\t\tif span.ParentSpanID().IsEmpty() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tspanServiceName := getServiceNameFromResource(resourceSpan.Resource())\n\t\t\t\t\tparentSpanServiceName, found := findServiceNameWithSpanId(traceWithTime.trace, span.ParentSpanID())\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tdepKey := parentSpanServiceName + \"&&&\" + spanServiceName\n\t\t\t\t\tif _, ok := deps[depKey]; !ok {\n\t\t\t\t\t\tdeps[depKey] = &model.DependencyLink{\n\t\t\t\t\t\t\tParent:    parentSpanServiceName,\n\t\t\t\t\t\t\tChild:     spanServiceName,\n\t\t\t\t\t\t\tCallCount: 1,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdeps[depKey].CallCount++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tretMe := make([]model.DependencyLink, 0, len(deps))\n\tfor _, dep := range deps {\n\t\tretMe = append(retMe, *dep)\n\t}\n\treturn retMe, nil\n}\n\nfunc findServiceNameWithSpanId(trace ptrace.Traces, spanId pcommon.SpanID) (string, bool) {\n\tfor _, resourceSpan := range trace.ResourceSpans().All() {\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif span.SpanID() == spanId {\n\t\t\t\t\treturn getServiceNameFromResource(resourceSpan.Resource()), true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc validTrace(td ptrace.Traces, query tracestore.TraceQueryParams) bool {\n\tfor _, resourceSpan := range td.ResourceSpans().All() {\n\t\tif !validResource(resourceSpan.Resource(), query) {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, scopeSpan := range resourceSpan.ScopeSpans().All() {\n\t\t\tfor _, span := range scopeSpan.Spans().All() {\n\t\t\t\tif validSpan(resourceSpan.Resource().Attributes(), scopeSpan.Scope(), span, query) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc validResource(resource pcommon.Resource, query tracestore.TraceQueryParams) bool {\n\treturn query.ServiceName == \"\" || query.ServiceName == getServiceNameFromResource(resource)\n}\n\nfunc validSpan(resourceAttributes pcommon.Map, scope pcommon.InstrumentationScope, span ptrace.Span, query tracestore.TraceQueryParams) bool {\n\tif query.OperationName != \"\" && query.OperationName != span.Name() {\n\t\treturn false\n\t}\n\n\tstartTime := span.StartTimestamp().AsTime()\n\tif !query.StartTimeMin.IsZero() && startTime.Before(query.StartTimeMin) {\n\t\treturn false\n\t}\n\tif !query.StartTimeMax.IsZero() && startTime.After(query.StartTimeMax) {\n\t\treturn false\n\t}\n\tduration := span.EndTimestamp().AsTime().Sub(startTime)\n\tif query.DurationMin != 0 && duration < query.DurationMin {\n\t\treturn false\n\t}\n\tif query.DurationMax != 0 && duration > query.DurationMax {\n\t\treturn false\n\t}\n\n\tif errAttribute, ok := query.Attributes.Get(errorAttribute); ok {\n\t\tif errAttribute.Bool() && span.Status().Code() != ptrace.StatusCodeError {\n\t\t\treturn false\n\t\t}\n\t\tif !errAttribute.Bool() && span.Status().Code() != ptrace.StatusCodeOk {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif statusAttr, ok := query.Attributes.Get(\"span.status\"); ok {\n\t\texpectedStatus := spanStatusFromString(statusAttr.AsString())\n\t\tif expectedStatus != span.Status().Code() {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif kindAttr, ok := query.Attributes.Get(\"span.kind\"); ok {\n\t\texpectedKind := spanKindFromString(kindAttr.AsString())\n\t\tif expectedKind != span.Kind() {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif scopeNameAttr, ok := query.Attributes.Get(\"scope.name\"); ok {\n\t\tif scopeNameAttr.AsString() != scope.Name() {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif scopeVersionAttr, ok := query.Attributes.Get(\"scope.version\"); ok {\n\t\tif scopeVersionAttr.AsString() != scope.Version() {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor key, val := range query.Attributes.All() {\n\t\tif key == errorAttribute ||\n\t\t\tkey == \"span.status\" ||\n\t\t\tkey == \"span.kind\" ||\n\t\t\tkey == \"scope.name\" ||\n\t\t\tkey == \"scope.version\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif resourceKey, ok := strings.CutPrefix(key, \"resource.\"); ok {\n\t\t\tif !matchAttributes(resourceKey, val, resourceAttributes) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif !findKeyValInTrace(key, val, resourceAttributes, scope.Attributes(), span) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc matchAttributes(key string, val pcommon.Value, attrs pcommon.Map) bool {\n\tif queryValue, ok := attrs.Get(key); ok {\n\t\treturn queryValue.AsString() == val.AsString()\n\t}\n\treturn false\n}\n\nfunc findKeyValInTrace(key string, val pcommon.Value, resourceAttributes pcommon.Map, scopeAttributes pcommon.Map, span ptrace.Span) bool {\n\ttagsMatched := matchAttributes(key, val, span.Attributes()) || matchAttributes(key, val, scopeAttributes) || matchAttributes(key, val, resourceAttributes)\n\tif tagsMatched {\n\t\treturn true\n\t}\n\tfor _, event := range span.Events().All() {\n\t\tif matchAttributes(key, val, event.Attributes()) {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, link := range span.Links().All() {\n\t\tif matchAttributes(key, val, link.Attributes()) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn tagsMatched\n}\n\nfunc fromOTELSpanKind(kind ptrace.SpanKind) string {\n\tif kind == ptrace.SpanKindUnspecified {\n\t\treturn \"\"\n\t}\n\treturn strings.ToLower(kind.String())\n}\n\nfunc spanStatusFromString(statusStr string) ptrace.StatusCode {\n\tswitch strings.ToUpper(statusStr) {\n\tcase \"OK\":\n\t\treturn ptrace.StatusCodeOk\n\tcase \"ERROR\":\n\t\treturn ptrace.StatusCodeError\n\tdefault:\n\t\treturn ptrace.StatusCodeUnset\n\t}\n}\n\nfunc spanKindFromString(kindStr string) ptrace.SpanKind {\n\tswitch strings.ToUpper(kindStr) {\n\tcase \"CLIENT\":\n\t\treturn ptrace.SpanKindClient\n\tcase \"SERVER\":\n\t\treturn ptrace.SpanKindServer\n\tcase \"PRODUCER\":\n\t\treturn ptrace.SpanKindProducer\n\tcase \"CONSUMER\":\n\t\treturn ptrace.SpanKindConsumer\n\tcase \"INTERNAL\":\n\t\treturn ptrace.SpanKindInternal\n\tdefault:\n\t\treturn ptrace.SpanKindUnspecified\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/README.md",
    "content": "# Storage Factory Converter\n\nA temporary v1 storage factory wrapper to implement v2 storage APIs.\nThis way, the existing v1 storage factories declared in `jaegerstorageextension`\ncan act as v2 storage while we migrate to v2 storage APIs.\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/depreader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n)\n\ntype DependencyReader struct {\n\treader dependencystore.Reader\n}\n\nfunc GetV1DependencyReader(reader depstore.Reader) dependencystore.Reader {\n\tif dr, ok := reader.(*DependencyReader); ok {\n\t\treturn dr.reader\n\t}\n\treturn &DowngradedDependencyReader{\n\t\treader: reader,\n\t}\n}\n\nfunc NewDependencyReader(reader dependencystore.Reader) *DependencyReader {\n\treturn &DependencyReader{\n\t\treader: reader,\n\t}\n}\n\nfunc (dr *DependencyReader) GetDependencies(\n\tctx context.Context,\n\tquery depstore.QueryParameters,\n) ([]model.DependencyLink, error) {\n\treturn dr.reader.GetDependencies(ctx, query.EndTime, query.EndTime.Sub(query.StartTime))\n}\n\ntype DowngradedDependencyReader struct {\n\treader depstore.Reader\n}\n\nfunc (dr *DowngradedDependencyReader) GetDependencies(\n\tctx context.Context,\n\tendTs time.Time,\n\tlookback time.Duration,\n) ([]model.DependencyLink, error) {\n\treturn dr.reader.GetDependencies(ctx, depstore.QueryParameters{\n\t\tStartTime: endTs.Add(-lookback),\n\t\tEndTime:   endTs,\n\t})\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/depreader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tdependencystoremocks \"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore\"\n\tdepstoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/depstore/mocks\"\n)\n\nfunc TestGetV1DependencyReader(t *testing.T) {\n\tt.Run(\"wrapped v1 reader\", func(t *testing.T) {\n\t\treader := new(dependencystoremocks.Reader)\n\t\ttraceReader := &DependencyReader{\n\t\t\treader: reader,\n\t\t}\n\t\tv1Reader := GetV1DependencyReader(traceReader)\n\t\trequire.Equal(t, reader, v1Reader)\n\t})\n\n\tt.Run(\"native v2 reader\", func(t *testing.T) {\n\t\treader := new(depstoremocks.Reader)\n\t\tv1Reader := GetV1DependencyReader(reader)\n\t\trequire.IsType(t, &DowngradedDependencyReader{}, v1Reader)\n\t\trequire.Equal(t, reader, v1Reader.(*DowngradedDependencyReader).reader)\n\t})\n}\n\nfunc TestDependencyReader_GetDependencies(t *testing.T) {\n\tend := time.Now()\n\tstart := end.Add(-1 * time.Minute)\n\tquery := depstore.QueryParameters{\n\t\tStartTime: start,\n\t\tEndTime:   end,\n\t}\n\texpectedDeps := []model.DependencyLink{{Parent: \"parent\", Child: \"child\", CallCount: 12}}\n\tmr := new(dependencystoremocks.Reader)\n\tmr.On(\"GetDependencies\", mock.Anything, end, time.Minute).Return(expectedDeps, nil)\n\tdr := NewDependencyReader(mr)\n\tdeps, err := dr.GetDependencies(context.Background(), query)\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedDeps, deps)\n}\n\nfunc TestDowngradedDependencyReader_GetDependencies(t *testing.T) {\n\tend := time.Now()\n\tstart := end.Add(-1 * time.Minute)\n\tquery := depstore.QueryParameters{\n\t\tStartTime: start,\n\t\tEndTime:   end,\n\t}\n\texpectedDeps := []model.DependencyLink{{Parent: \"parent\", Child: \"child\", CallCount: 12}}\n\tmr := new(depstoremocks.Reader)\n\tmr.On(\"GetDependencies\", mock.Anything, query).Return(expectedDeps, nil)\n\tdr := &DowngradedDependencyReader{\n\t\treader: mr,\n\t}\n\tdeps, err := dr.GetDependencies(context.Background(), end, time.Minute)\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedDeps, deps)\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/factory.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/factory_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/otelids.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"encoding/binary\"\n\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\n// FromV1TraceID converts the TraceID to OTEL's representation of a trace identitfier.\n// This was taken from\n// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/internal/coreinternal/idutils/big_endian_converter.go.\nfunc FromV1TraceID(t model.TraceID) pcommon.TraceID {\n\ttraceID := [16]byte{}\n\tbinary.BigEndian.PutUint64(traceID[:8], t.High)\n\tbinary.BigEndian.PutUint64(traceID[8:], t.Low)\n\treturn traceID\n}\n\nfunc ToV1TraceID(traceID pcommon.TraceID) model.TraceID {\n\t// traceIDShortBytesLen indicates length of 64bit traceID when represented as list of bytes\n\tconst traceIDShortBytesLen = 8\n\n\treturn model.TraceID{\n\t\tHigh: binary.BigEndian.Uint64(traceID[:traceIDShortBytesLen]),\n\t\tLow:  binary.BigEndian.Uint64(traceID[traceIDShortBytesLen:]),\n\t}\n}\n\n// FromV1SpanID converts the SpanID to OTEL's representation of a span identitfier.\n// This was taken from\n// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/internal/coreinternal/idutils/big_endian_converter.go.\nfunc FromV1SpanID(s model.SpanID) pcommon.SpanID {\n\tspanID := [8]byte{}\n\tbinary.BigEndian.PutUint64(spanID[:], uint64(s))\n\treturn pcommon.SpanID(spanID)\n}\n\n// ToV1SpanID converts OTEL's SpanID to the model representation of a span identitfier.\n// This was taken from\n// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/internal/coreinternal/idutils/big_endian_converter.go.\nfunc ToV1SpanID(spanID pcommon.SpanID) model.SpanID {\n\treturn model.SpanID(binary.BigEndian.Uint64(spanID[:]))\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/otelids_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2018 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nfunc TestToOTELTraceID(t *testing.T) {\n\tmodelTraceID := model.TraceID{\n\t\tLow:  3,\n\t\tHigh: 2,\n\t}\n\totelTraceID := FromV1TraceID(modelTraceID)\n\texpected := []byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}\n\trequire.Equal(t, pcommon.TraceID(expected), otelTraceID)\n}\n\nfunc TestTraceIDFromOTEL(t *testing.T) {\n\totelTraceID := pcommon.TraceID([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3})\n\texpected := model.TraceID{\n\t\tLow:  3,\n\t\tHigh: 2,\n\t}\n\trequire.Equal(t, expected, ToV1TraceID(otelTraceID))\n}\n\nfunc TestToOTELSpanID(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tspanID   model.SpanID\n\t\texpected pcommon.SpanID\n\t}{\n\t\t{\n\t\t\tname:     \"zero span ID\",\n\t\t\tspanID:   model.NewSpanID(0),\n\t\t\texpected: pcommon.NewSpanIDEmpty(),\n\t\t},\n\t\t{\n\t\t\tname:     \"non-zero span ID\",\n\t\t\tspanID:   model.NewSpanID(1),\n\t\t\texpected: pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactual := FromV1SpanID(test.spanID)\n\t\t\tassert.Equal(t, test.expected, actual)\n\t\t})\n\t}\n}\n\nfunc TestSpanIDFromOTEL(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\totelSpanID pcommon.SpanID\n\t\texpected   model.SpanID\n\t}{\n\t\t{\n\t\t\tname:       \"zero span ID\",\n\t\t\totelSpanID: pcommon.NewSpanIDEmpty(),\n\t\t\texpected:   model.NewSpanID(0),\n\t\t},\n\t\t{\n\t\t\tname:       \"non-zero span ID\",\n\t\t\totelSpanID: pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}),\n\t\t\texpected:   model.NewSpanID(1),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactual := ToV1SpanID(test.otelSpanID)\n\t\t\tassert.Equal(t, test.expected, actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/spanreader.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar _ spanstore.Reader = (*SpanReader)(nil)\n\nvar errTooManyTracesFound = errors.New(\"too many traces found\")\n\n// SpanReader adapts a v2 tracestore.Reader to the v1 spanstore.Reader interface.\ntype SpanReader struct {\n\ttraceReader tracestore.Reader\n}\n\n// GetV1Reader adapts a v2 tracestore.Reader to the v1 spanstore.Reader interface.\n// If the passed reader is already a v1 adapter, it returns the underlying v1 spanstore.Reader.\nfunc GetV1Reader(reader tracestore.Reader) spanstore.Reader {\n\tif tr, ok := reader.(*TraceReader); ok {\n\t\treturn tr.spanReader\n\t}\n\treturn &SpanReader{\n\t\ttraceReader: reader,\n\t}\n}\n\nfunc (sr *SpanReader) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) {\n\tgetTracesIter := sr.traceReader.GetTraces(ctx, tracestore.GetTraceParams{\n\t\tTraceID: FromV1TraceID(query.TraceID),\n\t\tStart:   query.StartTime,\n\t\tEnd:     query.EndTime,\n\t})\n\ttraces, err := V1TracesFromSeq2(getTracesIter)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(traces) == 0 {\n\t\treturn nil, spanstore.ErrTraceNotFound\n\t} else if len(traces) > 1 {\n\t\treturn nil, errTooManyTracesFound\n\t}\n\treturn traces[0], nil\n}\n\nfunc (sr *SpanReader) GetServices(ctx context.Context) ([]string, error) {\n\treturn sr.traceReader.GetServices(ctx)\n}\n\nfunc (sr *SpanReader) GetOperations(\n\tctx context.Context,\n\tquery spanstore.OperationQueryParameters,\n) ([]spanstore.Operation, error) {\n\to, err := sr.traceReader.GetOperations(ctx, tracestore.OperationQueryParams{\n\t\tServiceName: query.ServiceName,\n\t\tSpanKind:    query.SpanKind,\n\t})\n\tif err != nil || o == nil {\n\t\treturn nil, err\n\t}\n\toperations := []spanstore.Operation{}\n\tfor _, operation := range o {\n\t\toperations = append(operations, spanstore.Operation{\n\t\t\tName:     operation.Name,\n\t\t\tSpanKind: operation.SpanKind,\n\t\t})\n\t}\n\treturn operations, nil\n}\n\nfunc (sr *SpanReader) FindTraces(\n\tctx context.Context,\n\tquery *spanstore.TraceQueryParameters,\n) ([]*model.Trace, error) {\n\tgetTracesIter := sr.traceReader.FindTraces(ctx, tracestore.TraceQueryParams{\n\t\tServiceName:   query.ServiceName,\n\t\tOperationName: query.OperationName,\n\t\tAttributes:    jptrace.PlainMapToPcommonMap(query.Tags),\n\t\tStartTimeMin:  query.StartTimeMin,\n\t\tStartTimeMax:  query.StartTimeMax,\n\t\tDurationMin:   query.DurationMin,\n\t\tDurationMax:   query.DurationMax,\n\t\tSearchDepth:   query.NumTraces,\n\t})\n\treturn V1TracesFromSeq2(getTracesIter)\n}\n\nfunc (sr *SpanReader) FindTraceIDs(\n\tctx context.Context,\n\tquery *spanstore.TraceQueryParameters,\n) ([]model.TraceID, error) {\n\ttraceIDsIter := sr.traceReader.FindTraceIDs(ctx, tracestore.TraceQueryParams{\n\t\tServiceName:   query.ServiceName,\n\t\tOperationName: query.OperationName,\n\t\tAttributes:    jptrace.PlainMapToPcommonMap(query.Tags),\n\t\tStartTimeMin:  query.StartTimeMin,\n\t\tStartTimeMax:  query.StartTimeMax,\n\t\tDurationMin:   query.DurationMin,\n\t\tDurationMax:   query.DurationMax,\n\t\tSearchDepth:   query.NumTraces,\n\t})\n\treturn V1TraceIDsFromSeq2(traceIDsIter)\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/spanreader_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"iter\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n\ttracestoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/mocks\"\n)\n\nfunc TestSpanReader_GetTrace(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tquery         spanstore.GetTraceParameters\n\t\texpectedQuery tracestore.GetTraceParams\n\t\ttraces        []ptrace.Traces\n\t\texpectedTrace *model.Trace\n\t\terr           error\n\t\texpectedErr   error\n\t}{\n\t\t{\n\t\t\tname: \"error getting trace\",\n\t\t\tquery: spanstore.GetTraceParameters{\n\t\t\t\tTraceID: model.NewTraceID(1, 2),\n\t\t\t},\n\t\t\texpectedQuery: tracestore.GetTraceParams{\n\t\t\t\tTraceID: [16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2},\n\t\t\t},\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"empty traces\",\n\t\t\tquery: spanstore.GetTraceParameters{\n\t\t\t\tTraceID: model.NewTraceID(1, 2),\n\t\t\t},\n\t\t\texpectedQuery: tracestore.GetTraceParams{\n\t\t\t\tTraceID: [16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2},\n\t\t\t},\n\t\t\ttraces:      []ptrace.Traces{},\n\t\t\texpectedErr: spanstore.ErrTraceNotFound,\n\t\t},\n\t\t{\n\t\t\tname: \"too many traces found\",\n\t\t\tquery: spanstore.GetTraceParameters{\n\t\t\t\tTraceID: model.NewTraceID(1, 2),\n\t\t\t},\n\t\t\texpectedQuery: tracestore.GetTraceParams{\n\t\t\t\tTraceID: [16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2},\n\t\t\t},\n\t\t\ttraces: func() []ptrace.Traces {\n\t\t\t\ttraces1 := ptrace.NewTraces()\n\t\t\t\tresources1 := traces1.ResourceSpans().AppendEmpty()\n\t\t\t\tresources1.Resource().Attributes().PutStr(\"service.name\", \"service1\")\n\t\t\t\tscopes1 := resources1.ScopeSpans().AppendEmpty()\n\t\t\t\tspan1 := scopes1.Spans().AppendEmpty()\n\t\t\t\tspan1.SetTraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2})\n\n\t\t\t\ttraces2 := ptrace.NewTraces()\n\t\t\t\tresources2 := traces2.ResourceSpans().AppendEmpty()\n\t\t\t\tresources2.Resource().Attributes().PutStr(\"service.name\", \"service1\")\n\t\t\t\tscopes2 := resources2.ScopeSpans().AppendEmpty()\n\t\t\t\tspan2 := scopes2.Spans().AppendEmpty()\n\t\t\t\tspan2.SetTraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3})\n\n\t\t\t\treturn []ptrace.Traces{traces1, traces2}\n\t\t\t}(),\n\t\t\texpectedErr: errTooManyTracesFound,\n\t\t},\n\t\t{\n\t\t\tname: \"success\",\n\t\t\tquery: spanstore.GetTraceParameters{\n\t\t\t\tTraceID: model.NewTraceID(1, 2),\n\t\t\t},\n\t\t\texpectedQuery: tracestore.GetTraceParams{\n\t\t\t\tTraceID: [16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2},\n\t\t\t},\n\t\t\ttraces: func() []ptrace.Traces {\n\t\t\t\ttraces := ptrace.NewTraces()\n\t\t\t\tresources := traces.ResourceSpans().AppendEmpty()\n\t\t\t\tresources.Resource().Attributes().PutStr(\"service.name\", \"service\")\n\t\t\t\tscopes := resources.ScopeSpans().AppendEmpty()\n\t\t\t\tspan := scopes.Spans().AppendEmpty()\n\t\t\t\tspan.SetTraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2})\n\t\t\t\tspan.SetSpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 3})\n\t\t\t\tspan.SetName(\"span\")\n\t\t\t\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, 0).UTC()))\n\t\t\t\treturn []ptrace.Traces{traces}\n\t\t\t}(),\n\t\t\texpectedTrace: &model.Trace{\n\t\t\t\tSpans: []*model.Span{\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceID:       model.NewTraceID(1, 2),\n\t\t\t\t\t\tSpanID:        model.NewSpanID(3),\n\t\t\t\t\t\tOperationName: \"span\",\n\t\t\t\t\t\tReferences:    []model.SpanRef{},\n\t\t\t\t\t\tTags:          make([]model.KeyValue, 0),\n\t\t\t\t\t\tProcess:       model.NewProcess(\"service\", make([]model.KeyValue, 0)),\n\t\t\t\t\t\tStartTime:     time.Unix(0, 0).UTC(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\ttr := tracestoremocks.Reader{}\n\t\ttr.On(\"GetTraces\", mock.Anything, mock.Anything).\n\t\t\tReturn(iter.Seq2[[]ptrace.Traces, error](func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\tyield(test.traces, test.err)\n\t\t\t})).Once()\n\n\t\tsr := SpanReader{\n\t\t\ttraceReader: &tr,\n\t\t}\n\t\ttrace, err := sr.GetTrace(context.Background(), test.query)\n\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\trequire.Equal(t, test.expectedTrace, trace)\n\t}\n}\n\nfunc TestSpanReader_GetServices(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tservices         []string\n\t\texpectedServices []string\n\t\terr              error\n\t\texpectedErr      error\n\t}{\n\t\t{\n\t\t\tname:        \"error getting services\",\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname:             \"no services\",\n\t\t\tservices:         []string{},\n\t\t\texpectedServices: []string{},\n\t\t},\n\t\t{\n\t\t\tname:             \"multiple services\",\n\t\t\tservices:         []string{\"service1\", \"service2\"},\n\t\t\texpectedServices: []string{\"service1\", \"service2\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\ttr := tracestoremocks.Reader{}\n\t\ttr.On(\"GetServices\", mock.Anything).\n\t\t\tReturn(test.services, test.err).Once()\n\n\t\tsr := SpanReader{\n\t\t\ttraceReader: &tr,\n\t\t}\n\t\tservices, err := sr.GetServices(context.Background())\n\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\trequire.Equal(t, test.expectedServices, services)\n\t}\n}\n\nfunc TestSpanReader_GetOperations(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\tquery              spanstore.OperationQueryParameters\n\t\texpectedQuery      tracestore.OperationQueryParams\n\t\toperations         []tracestore.Operation\n\t\texpectedOperations []spanstore.Operation\n\t\terr                error\n\t\texpectedErr        error\n\t}{\n\t\t{\n\t\t\tname: \"error getting operations\",\n\t\t\tquery: spanstore.OperationQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"no operations\",\n\t\t\tquery: spanstore.OperationQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\toperations:         []tracestore.Operation{},\n\t\t\texpectedOperations: []spanstore.Operation{},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple operations\",\n\t\t\tquery: spanstore.OperationQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.OperationQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\toperations: []tracestore.Operation{\n\t\t\t\t{Name: \"operation1\", SpanKind: \"kind1\"},\n\t\t\t\t{Name: \"operation2\", SpanKind: \"kind2\"},\n\t\t\t},\n\t\t\texpectedOperations: []spanstore.Operation{\n\t\t\t\t{Name: \"operation1\", SpanKind: \"kind1\"},\n\t\t\t\t{Name: \"operation2\", SpanKind: \"kind2\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\ttr := tracestoremocks.Reader{}\n\t\ttr.On(\"GetOperations\", mock.Anything, test.expectedQuery).\n\t\t\tReturn(test.operations, test.err).Once()\n\n\t\tsr := SpanReader{\n\t\t\ttraceReader: &tr,\n\t\t}\n\t\tops, err := sr.GetOperations(context.Background(), test.query)\n\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\trequire.Equal(t, test.expectedOperations, ops)\n\t}\n}\n\nfunc TestSpanReader_FindTraces(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tquery          *spanstore.TraceQueryParameters\n\t\texpectedQuery  tracestore.TraceQueryParams\n\t\ttraces         []ptrace.Traces\n\t\texpectedTraces []*model.Trace\n\t\terr            error\n\t\texpectedErr    error\n\t}{\n\t\t{\n\t\t\tname: \"error finding traces\",\n\t\t\tquery: &spanstore.TraceQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.TraceQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t\tAttributes:  pcommon.NewMap(),\n\t\t\t},\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"no traces found\",\n\t\t\tquery: &spanstore.TraceQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.TraceQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t\tAttributes:  pcommon.NewMap(),\n\t\t\t},\n\t\t\ttraces:         []ptrace.Traces{},\n\t\t\texpectedTraces: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"multiple traces found\",\n\t\t\tquery: &spanstore.TraceQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.TraceQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t\tAttributes:  pcommon.NewMap(),\n\t\t\t},\n\t\t\ttraces: func() []ptrace.Traces {\n\t\t\t\ttraces1 := ptrace.NewTraces()\n\t\t\t\tresources1 := traces1.ResourceSpans().AppendEmpty()\n\t\t\t\tresources1.Resource().Attributes().PutStr(\"service.name\", \"service1\")\n\t\t\t\tscopes1 := resources1.ScopeSpans().AppendEmpty()\n\t\t\t\tspan1 := scopes1.Spans().AppendEmpty()\n\t\t\t\tspan1.SetTraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2})\n\t\t\t\tspan1.SetSpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 3})\n\t\t\t\tspan1.SetName(\"span1\")\n\t\t\t\tspan1.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, 0).UTC()))\n\n\t\t\t\ttraces2 := ptrace.NewTraces()\n\t\t\t\tresources2 := traces2.ResourceSpans().AppendEmpty()\n\t\t\t\tresources2.Resource().Attributes().PutStr(\"service.name\", \"service1\")\n\t\t\t\tscopes2 := resources2.ScopeSpans().AppendEmpty()\n\t\t\t\tspan2 := scopes2.Spans().AppendEmpty()\n\t\t\t\tspan2.SetTraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5})\n\t\t\t\tspan2.SetSpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 6})\n\t\t\t\tspan2.SetName(\"span2\")\n\t\t\t\tspan2.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, 0).UTC()))\n\n\t\t\t\treturn []ptrace.Traces{traces1, traces2}\n\t\t\t}(),\n\t\t\texpectedTraces: []*model.Trace{\n\t\t\t\t{\n\t\t\t\t\tSpans: []*model.Span{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tTraceID:       model.NewTraceID(1, 2),\n\t\t\t\t\t\t\tSpanID:        model.NewSpanID(3),\n\t\t\t\t\t\t\tOperationName: \"span1\",\n\t\t\t\t\t\t\tReferences:    make([]model.SpanRef, 0),\n\t\t\t\t\t\t\tTags:          model.KeyValues{},\n\t\t\t\t\t\t\tProcess:       model.NewProcess(\"service1\", make([]model.KeyValue, 0)),\n\t\t\t\t\t\t\tStartTime:     time.Unix(0, 0).UTC(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSpans: []*model.Span{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tTraceID:       model.NewTraceID(4, 5),\n\t\t\t\t\t\t\tSpanID:        model.NewSpanID(6),\n\t\t\t\t\t\t\tOperationName: \"span2\",\n\t\t\t\t\t\t\tReferences:    make([]model.SpanRef, 0),\n\t\t\t\t\t\t\tTags:          model.KeyValues{},\n\t\t\t\t\t\t\tProcess:       model.NewProcess(\"service1\", make([]model.KeyValue, 0)),\n\t\t\t\t\t\t\tStartTime:     time.Unix(0, 0).UTC(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\ttr := tracestoremocks.Reader{}\n\t\ttr.On(\"FindTraces\", mock.Anything, test.expectedQuery).\n\t\t\tReturn(iter.Seq2[[]ptrace.Traces, error](func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\tyield(test.traces, test.err)\n\t\t\t})).Once()\n\n\t\tsr := SpanReader{\n\t\t\ttraceReader: &tr,\n\t\t}\n\t\ttraces, err := sr.FindTraces(context.Background(), test.query)\n\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\trequire.Equal(t, test.expectedTraces, traces)\n\t}\n}\n\nfunc TestSpanReader_FindTraceIDs(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tquery            *spanstore.TraceQueryParameters\n\t\texpectedQuery    tracestore.TraceQueryParams\n\t\ttraceIDs         []tracestore.FoundTraceID\n\t\texpectedTraceIDs []model.TraceID\n\t\terr              error\n\t\texpectedErr      error\n\t}{\n\t\t{\n\t\t\tname: \"error finding trace IDs\",\n\t\t\tquery: &spanstore.TraceQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.TraceQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t\tAttributes:  pcommon.NewMap(),\n\t\t\t},\n\t\t\terr:         assert.AnError,\n\t\t\texpectedErr: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"no trace IDs found\",\n\t\t\tquery: &spanstore.TraceQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.TraceQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t\tAttributes:  pcommon.NewMap(),\n\t\t\t},\n\t\t\ttraceIDs:         []tracestore.FoundTraceID{},\n\t\t\texpectedTraceIDs: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"multiple trace IDs found\",\n\t\t\tquery: &spanstore.TraceQueryParameters{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t},\n\t\t\texpectedQuery: tracestore.TraceQueryParams{\n\t\t\t\tServiceName: \"service1\",\n\t\t\t\tAttributes:  pcommon.NewMap(),\n\t\t\t},\n\t\t\ttraceIDs: []tracestore.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceID: [16]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTraceID: [16]byte{0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedTraceIDs: []model.TraceID{\n\t\t\t\tmodel.NewTraceID(1, 2),\n\t\t\t\tmodel.NewTraceID(3, 4),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\ttr := tracestoremocks.Reader{}\n\t\ttr.On(\"FindTraceIDs\", mock.Anything, test.expectedQuery).\n\t\t\tReturn(iter.Seq2[[]tracestore.FoundTraceID, error](func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\t\t\tyield(test.traceIDs, test.err)\n\t\t\t})).Once()\n\n\t\tsr := SpanReader{\n\t\t\ttraceReader: &tr,\n\t\t}\n\t\ttraceIDs, err := sr.FindTraceIDs(context.Background(), test.query)\n\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\trequire.Equal(t, test.expectedTraceIDs, traceIDs)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/spanwriter.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar _ spanstore.Writer = (*SpanWriter)(nil)\n\n// SpanReader wraps a tracestore.Writer so that it can be downgraded to implement\n// the v1 spanstore.Writer interface.\ntype SpanWriter struct {\n\ttraceWriter tracestore.Writer\n}\n\nfunc (sw *SpanWriter) WriteSpan(ctx context.Context, span *model.Span) error {\n\ttraces := V1BatchesToTraces([]*model.Batch{{Spans: []*model.Span{span}}})\n\treturn sw.traceWriter.WriteTraces(ctx, traces)\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/spanwriter_test.go",
    "content": "// Copyright (c) 2025 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\tspanstoremocks \"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/mocks\"\n\ttracestoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/mocks\"\n)\n\nfunc TestGetV1Reader(t *testing.T) {\n\tt.Run(\"wrapped v1 reader\", func(t *testing.T) {\n\t\treader := new(spanstoremocks.Reader)\n\t\ttraceReader := &TraceReader{\n\t\t\tspanReader: reader,\n\t\t}\n\t\tv1Reader := GetV1Reader(traceReader)\n\t\trequire.Equal(t, reader, v1Reader)\n\t})\n\n\tt.Run(\"native v2 reader\", func(t *testing.T) {\n\t\treader := new(tracestoremocks.Reader)\n\t\tv1Reader := GetV1Reader(reader)\n\t\trequire.IsType(t, &SpanReader{}, v1Reader)\n\t\trequire.Equal(t, reader, v1Reader.(*SpanReader).traceReader)\n\t})\n}\n\nfunc TestSpanWriter_WriteSpan(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tmockReturnErr error\n\t\texpectedErr   error\n\t}{\n\t\t{\n\t\t\tname:          \"success\",\n\t\t\tmockReturnErr: nil,\n\t\t\texpectedErr:   nil,\n\t\t},\n\t\t{\n\t\t\tname:          \"error in translator\",\n\t\t\tmockReturnErr: assert.AnError,\n\t\t\texpectedErr:   assert.AnError,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmockTraceWriter := &tracestoremocks.Writer{}\n\t\t\tspanWriter := &SpanWriter{\n\t\t\t\ttraceWriter: mockTraceWriter,\n\t\t\t}\n\n\t\t\tnow := time.Now().UTC()\n\t\t\ttestSpan := &model.Span{\n\t\t\t\tTraceID:   model.NewTraceID(0, 1),\n\t\t\t\tSpanID:    model.NewSpanID(1),\n\t\t\t\tStartTime: now,\n\t\t\t\tDuration:  time.Second,\n\t\t\t}\n\n\t\t\ttraces := ptrace.NewTraces()\n\t\t\tresources := traces.ResourceSpans().AppendEmpty()\n\t\t\tscopes := resources.ScopeSpans().AppendEmpty()\n\t\t\tspan := scopes.Spans().AppendEmpty()\n\t\t\tspan.SetTraceID(pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}))\n\t\t\tspan.SetSpanID(pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}))\n\t\t\tspan.SetStartTimestamp(pcommon.NewTimestampFromTime(now))\n\t\t\tspan.SetEndTimestamp(pcommon.NewTimestampFromTime(now.Add(time.Second)))\n\n\t\t\tmockTraceWriter.On(\"WriteTraces\", mock.Anything, traces).Return(test.mockReturnErr)\n\n\t\t\terr := spanWriter.WriteSpan(context.Background(), testSpan)\n\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/tracereader.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"iter\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nvar _ tracestore.Reader = (*TraceReader)(nil)\n\n// TraceReader adapts a v1 spanstore.Reader to the v2 tracestore.Reader interface.\ntype TraceReader struct {\n\tspanReader spanstore.Reader\n}\n\nfunc NewTraceReader(spanReader spanstore.Reader) *TraceReader {\n\treturn &TraceReader{\n\t\tspanReader: spanReader,\n\t}\n}\n\nfunc (tr *TraceReader) GetTraces(\n\tctx context.Context,\n\ttraceIDs ...tracestore.GetTraceParams,\n) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\tfor _, idParams := range traceIDs {\n\t\t\tquery := spanstore.GetTraceParameters{\n\t\t\t\tTraceID:   ToV1TraceID(idParams.TraceID),\n\t\t\t\tStartTime: idParams.Start,\n\t\t\t\tEndTime:   idParams.End,\n\t\t\t}\n\t\t\tt, err := tr.spanReader.GetTrace(ctx, query)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, spanstore.ErrTraceNotFound) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tyield(nil, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbatch := &model.Batch{Spans: t.GetSpans()}\n\t\t\ttr := V1BatchesToTraces([]*model.Batch{batch})\n\t\t\tif !yield([]ptrace.Traces{tr}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (tr *TraceReader) GetServices(ctx context.Context) ([]string, error) {\n\treturn tr.spanReader.GetServices(ctx)\n}\n\nfunc (tr *TraceReader) GetOperations(\n\tctx context.Context,\n\tquery tracestore.OperationQueryParams,\n) ([]tracestore.Operation, error) {\n\to, err := tr.spanReader.GetOperations(ctx, spanstore.OperationQueryParameters{\n\t\tServiceName: query.ServiceName,\n\t\tSpanKind:    query.SpanKind,\n\t})\n\tif err != nil || o == nil {\n\t\treturn nil, err\n\t}\n\toperations := []tracestore.Operation{}\n\tfor _, operation := range o {\n\t\toperations = append(operations, tracestore.Operation{\n\t\t\tName:     operation.Name,\n\t\t\tSpanKind: operation.SpanKind,\n\t\t})\n\t}\n\treturn operations, nil\n}\n\nfunc (tr *TraceReader) FindTraces(\n\tctx context.Context,\n\tquery tracestore.TraceQueryParams,\n) iter.Seq2[[]ptrace.Traces, error] {\n\treturn func(yield func([]ptrace.Traces, error) bool) {\n\t\ttraces, err := tr.spanReader.FindTraces(ctx, query.ToSpanStoreQueryParameters())\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\tfor _, trace := range traces {\n\t\t\tbatch := &model.Batch{Spans: trace.GetSpans()}\n\t\t\totelTrace := V1BatchesToTraces([]*model.Batch{batch})\n\t\t\tif !yield([]ptrace.Traces{otelTrace}, nil) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (tr *TraceReader) FindTraceIDs(\n\tctx context.Context,\n\tquery tracestore.TraceQueryParams,\n) iter.Seq2[[]tracestore.FoundTraceID, error] {\n\treturn func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\ttraceIDs, err := tr.spanReader.FindTraceIDs(ctx, query.ToSpanStoreQueryParameters())\n\t\tif err != nil {\n\t\t\tyield(nil, err)\n\t\t\treturn\n\t\t}\n\t\totelIDs := make([]tracestore.FoundTraceID, 0, len(traceIDs))\n\t\tfor _, traceID := range traceIDs {\n\t\t\totelIDs = append(otelIDs, tracestore.FoundTraceID{\n\t\t\t\tTraceID: FromV1TraceID(traceID),\n\t\t\t})\n\t\t}\n\t\tyield(otelIDs, nil)\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/tracereader_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jiter\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\tspanstoremocks \"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nfunc TestTraceReader_GetTracesDelegatesSuccessResponse(t *testing.T) {\n\tsr := new(spanstoremocks.Reader)\n\tmodelTrace := &model.Trace{\n\t\tSpans: []*model.Span{\n\t\t\t{\n\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\tSpanID:        model.SpanID(1),\n\t\t\t\tOperationName: \"operation-a\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\tSpanID:        model.SpanID(2),\n\t\t\t\tOperationName: \"operation-b\",\n\t\t\t},\n\t\t},\n\t}\n\texpectedQuery := spanstore.GetTraceParameters{TraceID: model.NewTraceID(2, 3)}\n\tsr.On(\"GetTrace\", mock.Anything, expectedQuery).Return(modelTrace, nil)\n\ttraceReader := &TraceReader{\n\t\tspanReader: sr,\n\t}\n\ttraces, err := jiter.FlattenWithErrors(traceReader.GetTraces(\n\t\tcontext.Background(),\n\t\ttracestore.GetTraceParams{\n\t\t\tTraceID: pcommon.TraceID([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}),\n\t\t},\n\t))\n\trequire.NoError(t, err)\n\trequire.Len(t, traces, 1)\n\ttrace := traces[0]\n\ttraceSpans := trace.ResourceSpans().At(0).ScopeSpans().At(0).Spans()\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}, traceSpans.At(0).TraceID())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 1}, traceSpans.At(0).SpanID())\n\trequire.Equal(t, \"operation-a\", traceSpans.At(0).Name())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}, traceSpans.At(1).TraceID())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 2}, traceSpans.At(1).SpanID())\n\trequire.Equal(t, \"operation-b\", traceSpans.At(1).Name())\n}\n\nfunc TestTraceReader_GetTracesErrorResponse(t *testing.T) {\n\ttestCases := []struct {\n\t\tname          string\n\t\tfirstErr      error\n\t\texpectedErr   error\n\t\texpectedIters int\n\t}{\n\t\t{\n\t\t\tname:          \"real error aborts iterator\",\n\t\t\tfirstErr:      assert.AnError,\n\t\t\texpectedErr:   assert.AnError,\n\t\t\texpectedIters: 0, // technically 1 but FlattenWithErrors makes it 0\n\t\t},\n\t\t{\n\t\t\tname:          \"trace not found error skips iteration\",\n\t\t\tfirstErr:      spanstore.ErrTraceNotFound,\n\t\t\texpectedErr:   nil,\n\t\t\texpectedIters: 1,\n\t\t},\n\t\t{\n\t\t\tname:          \"no error produces two iterations\",\n\t\t\tfirstErr:      nil,\n\t\t\texpectedErr:   nil,\n\t\t\texpectedIters: 2,\n\t\t},\n\t}\n\ttraceID := func(i byte) tracestore.GetTraceParams {\n\t\treturn tracestore.GetTraceParams{\n\t\t\tTraceID: pcommon.TraceID([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, i}),\n\t\t}\n\t}\n\tfor _, test := range testCases {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsr := new(spanstoremocks.Reader)\n\t\t\tsr.On(\"GetTrace\", mock.Anything, mock.Anything).Return(&model.Trace{}, test.firstErr).Once()\n\t\t\tsr.On(\"GetTrace\", mock.Anything, mock.Anything).Return(&model.Trace{}, nil).Once()\n\t\t\ttraceReader := &TraceReader{\n\t\t\t\tspanReader: sr,\n\t\t\t}\n\t\t\ttraces, err := jiter.FlattenWithErrors(traceReader.GetTraces(\n\t\t\t\tcontext.Background(), traceID(1), traceID(2),\n\t\t\t))\n\t\t\trequire.ErrorIs(t, err, test.expectedErr)\n\t\t\tassert.Len(t, traces, test.expectedIters)\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_GetTracesEarlyStop(t *testing.T) {\n\tsr := new(spanstoremocks.Reader)\n\tsr.On(\n\t\t\"GetTrace\",\n\t\tmock.Anything,\n\t\tmock.Anything,\n\t).Return(&model.Trace{}, nil)\n\ttraceReader := &TraceReader{\n\t\tspanReader: sr,\n\t}\n\ttraceID := func(i byte) tracestore.GetTraceParams {\n\t\treturn tracestore.GetTraceParams{\n\t\t\tTraceID: pcommon.TraceID([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, i}),\n\t\t}\n\t}\n\tcalled := 0\n\ttraceReader.GetTraces(\n\t\tcontext.Background(), traceID(1), traceID(2), traceID(3),\n\t)(func(tr []ptrace.Traces, err error) bool {\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tr, 1)\n\t\tcalled++\n\t\treturn true\n\t})\n\tassert.Equal(t, 3, called)\n\tcalled = 0\n\ttraceReader.GetTraces(\n\t\tcontext.Background(), traceID(1), traceID(2), traceID(3),\n\t)(func(tr []ptrace.Traces, err error) bool {\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tr, 1)\n\t\tcalled++\n\t\treturn false // early return\n\t})\n\tassert.Equal(t, 1, called)\n}\n\nfunc TestTraceReader_GetServicesDelegatesToSpanReader(t *testing.T) {\n\tsr := new(spanstoremocks.Reader)\n\texpectedServices := []string{\"service-a\", \"service-b\"}\n\tsr.On(\"GetServices\", mock.Anything).Return(expectedServices, nil)\n\ttraceReader := &TraceReader{\n\t\tspanReader: sr,\n\t}\n\tservices, err := traceReader.GetServices(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, expectedServices, services)\n}\n\nfunc TestTraceReader_GetOperationsDelegatesResponse(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\toperations         []spanstore.Operation\n\t\texpectedOperations []tracestore.Operation\n\t\terr                error\n\t}{\n\t\t{\n\t\t\tname: \"successful response\",\n\t\t\toperations: []spanstore.Operation{\n\t\t\t\t{\n\t\t\t\t\tName:     \"operation-a\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"operation-b\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedOperations: []tracestore.Operation{\n\t\t\t\t{\n\t\t\t\t\tName:     \"operation-a\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"operation-b\",\n\t\t\t\t\tSpanKind: \"server\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"nil response\",\n\t\t\toperations:         nil,\n\t\t\texpectedOperations: nil,\n\t\t},\n\t\t{\n\t\t\tname:               \"empty response\",\n\t\t\toperations:         []spanstore.Operation{},\n\t\t\texpectedOperations: []tracestore.Operation{},\n\t\t},\n\t\t{\n\t\t\tname:               \"error response\",\n\t\t\toperations:         nil,\n\t\t\texpectedOperations: nil,\n\t\t\terr:                errors.New(\"test error\"),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsr := new(spanstoremocks.Reader)\n\t\t\tsr.On(\"GetOperations\",\n\t\t\t\tmock.Anything,\n\t\t\t\tspanstore.OperationQueryParameters{\n\t\t\t\t\tServiceName: \"service-a\",\n\t\t\t\t\tSpanKind:    \"server\",\n\t\t\t\t}).Return(test.operations, test.err)\n\t\t\ttraceReader := &TraceReader{\n\t\t\t\tspanReader: sr,\n\t\t\t}\n\t\t\toperations, err := traceReader.GetOperations(\n\t\t\t\tcontext.Background(),\n\t\t\t\ttracestore.OperationQueryParams{\n\t\t\t\t\tServiceName: \"service-a\",\n\t\t\t\t\tSpanKind:    \"server\",\n\t\t\t\t})\n\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\trequire.Equal(t, test.expectedOperations, operations)\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_FindTracesDelegatesSuccessResponse(t *testing.T) {\n\tmodelTraces := []*model.Trace{\n\t\t{\n\t\t\tSpans: []*model.Span{\n\t\t\t\t{\n\t\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\t\tSpanID:        model.SpanID(1),\n\t\t\t\t\tOperationName: \"operation-a\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTraceID:       model.NewTraceID(4, 5),\n\t\t\t\t\tSpanID:        model.SpanID(2),\n\t\t\t\t\tOperationName: \"operation-b\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tSpans: []*model.Span{\n\t\t\t\t{\n\t\t\t\t\tTraceID:       model.NewTraceID(6, 7),\n\t\t\t\t\tSpanID:        model.SpanID(3),\n\t\t\t\t\tOperationName: \"operation-c\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tsr := new(spanstoremocks.Reader)\n\tnow := time.Now()\n\tsr.On(\n\t\t\"FindTraces\",\n\t\tmock.Anything,\n\t\t&spanstore.TraceQueryParameters{\n\t\t\tServiceName:   \"service\",\n\t\t\tOperationName: \"operation\",\n\t\t\tTags:          map[string]string{\"tag-a\": \"val-a\"},\n\t\t\tStartTimeMin:  now,\n\t\t\tStartTimeMax:  now.Add(time.Minute),\n\t\t\tDurationMin:   time.Minute,\n\t\t\tDurationMax:   time.Hour,\n\t\t\tNumTraces:     10,\n\t\t},\n\t).Return(modelTraces, nil)\n\ttraceReader := &TraceReader{\n\t\tspanReader: sr,\n\t}\n\tattributes := pcommon.NewMap()\n\tattributes.PutStr(\"tag-a\", \"val-a\")\n\ttraces, err := jiter.FlattenWithErrors(traceReader.FindTraces(\n\t\tcontext.Background(),\n\t\ttracestore.TraceQueryParams{\n\t\t\tServiceName:   \"service\",\n\t\t\tOperationName: \"operation\",\n\t\t\tAttributes:    attributes,\n\t\t\tStartTimeMin:  now,\n\t\t\tStartTimeMax:  now.Add(time.Minute),\n\t\t\tDurationMin:   time.Minute,\n\t\t\tDurationMax:   time.Hour,\n\t\t\tSearchDepth:   10,\n\t\t},\n\t))\n\trequire.NoError(t, err)\n\trequire.Len(t, traces, len(modelTraces))\n\ttraceASpans := traces[0].ResourceSpans().At(0).ScopeSpans().At(0).Spans()\n\ttraceBSpans := traces[1].ResourceSpans().At(0).ScopeSpans().At(0).Spans()\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}, traceASpans.At(0).TraceID())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 1}, traceASpans.At(0).SpanID())\n\trequire.Equal(t, \"operation-a\", traceASpans.At(0).Name())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5}, traceASpans.At(1).TraceID())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 2}, traceASpans.At(1).SpanID())\n\trequire.Equal(t, \"operation-b\", traceASpans.At(1).Name())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7}, traceBSpans.At(0).TraceID())\n\trequire.EqualValues(t, []byte{0, 0, 0, 0, 0, 0, 0, 3}, traceBSpans.At(0).SpanID())\n\trequire.Equal(t, \"operation-c\", traceBSpans.At(0).Name())\n}\n\nfunc TestTraceReader_FindTracesEdgeCases(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tmodelTraces    []*model.Trace\n\t\texpectedTraces []ptrace.Traces\n\t\terr            error\n\t}{\n\t\t{\n\t\t\tname:           \"nil response\",\n\t\t\tmodelTraces:    nil,\n\t\t\texpectedTraces: nil,\n\t\t},\n\t\t{\n\t\t\tname:           \"empty response\",\n\t\t\tmodelTraces:    []*model.Trace{},\n\t\t\texpectedTraces: nil,\n\t\t},\n\t\t{\n\t\t\tname:           \"error response\",\n\t\t\tmodelTraces:    nil,\n\t\t\texpectedTraces: nil,\n\t\t\terr:            errors.New(\"test error\"),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsr := new(spanstoremocks.Reader)\n\t\t\tsr.On(\n\t\t\t\t\"FindTraces\",\n\t\t\t\tmock.Anything,\n\t\t\t\tmock.Anything,\n\t\t\t).Return(test.modelTraces, test.err)\n\t\t\ttraceReader := &TraceReader{\n\t\t\t\tspanReader: sr,\n\t\t\t}\n\t\t\ttraces, err := jiter.FlattenWithErrors(traceReader.FindTraces(\n\t\t\t\tcontext.Background(),\n\t\t\t\ttracestore.TraceQueryParams{\n\t\t\t\t\tAttributes: pcommon.NewMap(),\n\t\t\t\t},\n\t\t\t))\n\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\trequire.Equal(t, test.expectedTraces, traces)\n\t\t})\n\t}\n}\n\nfunc TestTraceReader_FindTracesEarlyStop(t *testing.T) {\n\tsr := new(spanstoremocks.Reader)\n\tsr.On(\n\t\t\"FindTraces\",\n\t\tmock.Anything,\n\t\tmock.Anything,\n\t).Return([]*model.Trace{{}, {}, {}}, nil).Twice()\n\ttraceReader := &TraceReader{\n\t\tspanReader: sr,\n\t}\n\tcalled := 0\n\ttraceReader.FindTraces(\n\t\tcontext.Background(), tracestore.TraceQueryParams{\n\t\t\tAttributes: pcommon.NewMap(),\n\t\t},\n\t)(func(tr []ptrace.Traces, err error) bool {\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tr, 1)\n\t\tcalled++\n\t\treturn true\n\t})\n\tassert.Equal(t, 3, called)\n\tcalled = 0\n\ttraceReader.FindTraces(\n\t\tcontext.Background(), tracestore.TraceQueryParams{\n\t\t\tAttributes: pcommon.NewMap(),\n\t\t},\n\t)(func(tr []ptrace.Traces, err error) bool {\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, tr, 1)\n\t\tcalled++\n\t\treturn false // early return\n\t})\n\tassert.Equal(t, 1, called)\n}\n\nfunc TestTraceReader_FindTraceIDsDelegatesResponse(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tmodelTraceIDs    []model.TraceID\n\t\texpectedTraceIDs []tracestore.FoundTraceID\n\t\terr              error\n\t}{\n\t\t{\n\t\t\tname: \"successful response\",\n\t\t\tmodelTraceIDs: []model.TraceID{\n\t\t\t\t{Low: 3, High: 2},\n\t\t\t\t{Low: 4, High: 3},\n\t\t\t},\n\t\t\texpectedTraceIDs: []tracestore.FoundTraceID{\n\t\t\t\t{\n\t\t\t\t\tTraceID: pcommon.TraceID([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTraceID: pcommon.TraceID([]byte{0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4}),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"empty response\",\n\t\t\tmodelTraceIDs:    []model.TraceID{},\n\t\t\texpectedTraceIDs: nil,\n\t\t},\n\t\t{\n\t\t\tname:             \"nil response\",\n\t\t\tmodelTraceIDs:    nil,\n\t\t\texpectedTraceIDs: nil,\n\t\t},\n\t\t{\n\t\t\tname:             \"error response\",\n\t\t\tmodelTraceIDs:    nil,\n\t\t\texpectedTraceIDs: nil,\n\t\t\terr:              errors.New(\"test error\"),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tsr := new(spanstoremocks.Reader)\n\t\t\tnow := time.Now()\n\t\t\tsr.On(\n\t\t\t\t\"FindTraceIDs\",\n\t\t\t\tmock.Anything,\n\t\t\t\t&spanstore.TraceQueryParameters{\n\t\t\t\t\tServiceName:   \"service\",\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t\tTags:          map[string]string{\"tag-a\": \"val-a\"},\n\t\t\t\t\tStartTimeMin:  now,\n\t\t\t\t\tStartTimeMax:  now.Add(time.Minute),\n\t\t\t\t\tDurationMin:   time.Minute,\n\t\t\t\t\tDurationMax:   time.Hour,\n\t\t\t\t\tNumTraces:     10,\n\t\t\t\t},\n\t\t\t).Return(test.modelTraceIDs, test.err)\n\t\t\ttraceReader := &TraceReader{\n\t\t\t\tspanReader: sr,\n\t\t\t}\n\t\t\tattributes := pcommon.NewMap()\n\t\t\tattributes.PutStr(\"tag-a\", \"val-a\")\n\t\t\ttraceIDs, err := jiter.FlattenWithErrors(traceReader.FindTraceIDs(\n\t\t\t\tcontext.Background(),\n\t\t\t\ttracestore.TraceQueryParams{\n\t\t\t\t\tServiceName:   \"service\",\n\t\t\t\t\tOperationName: \"operation\",\n\t\t\t\t\tAttributes:    attributes,\n\t\t\t\t\tStartTimeMin:  now,\n\t\t\t\t\tStartTimeMax:  now.Add(time.Minute),\n\t\t\t\t\tDurationMin:   time.Minute,\n\t\t\t\t\tDurationMax:   time.Hour,\n\t\t\t\t\tSearchDepth:   10,\n\t\t\t\t},\n\t\t\t))\n\t\t\trequire.ErrorIs(t, err, test.err)\n\t\t\trequire.Equal(t, test.expectedTraceIDs, traceIDs)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/tracewriter.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/dependencystore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\ntype TraceWriter struct {\n\tspanWriter spanstore.Writer\n}\n\nfunc GetV1Writer(writer tracestore.Writer) spanstore.Writer {\n\tif tr, ok := writer.(*TraceWriter); ok {\n\t\treturn tr.spanWriter\n\t}\n\treturn &SpanWriter{\n\t\ttraceWriter: writer,\n\t}\n}\n\nfunc NewTraceWriter(spanWriter spanstore.Writer) *TraceWriter {\n\treturn &TraceWriter{\n\t\tspanWriter: spanWriter,\n\t}\n}\n\n// WriteTraces implements tracestore.Writer.\nfunc (t *TraceWriter) WriteTraces(ctx context.Context, td ptrace.Traces) error {\n\tbatches := V1BatchesFromTraces(td)\n\tvar errs []error\n\tfor _, batch := range batches {\n\t\tfor _, span := range batch.Spans {\n\t\t\tif span.Process == nil {\n\t\t\t\tspan.Process = batch.Process\n\t\t\t}\n\t\t\terr := t.spanWriter.WriteSpan(ctx, span)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.Join(errs...)\n}\n\ntype DependencyWriter struct {\n\twriter dependencystore.Writer\n}\n\nfunc NewDependencyWriter(writer dependencystore.Writer) *DependencyWriter {\n\treturn &DependencyWriter{\n\t\twriter: writer,\n\t}\n}\n\nfunc (dw *DependencyWriter) WriteDependencies(ts time.Time, dependencies []model.DependencyLink) error {\n\treturn dw.writer.WriteDependencies(ts, dependencies)\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/tracewriter_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore\"\n\tspanstoremocks \"github.com/jaegertracing/jaeger/internal/storage/v1/api/spanstore/mocks\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v1/badger\"\n\ttracestoremocks \"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore/mocks\"\n)\n\nfunc TestWriteTraces(t *testing.T) {\n\tf := badger.NewFactory()\n\terr := f.Initialize(metrics.NullFactory, zap.NewNop())\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\trequire.NoError(t, f.Close())\n\t}()\n\n\tspanWriter, err := f.CreateSpanWriter()\n\trequire.NoError(t, err)\n\tspanReader, err := f.CreateSpanReader()\n\trequire.NoError(t, err)\n\ttraceWriter := &TraceWriter{\n\t\tspanWriter: spanWriter,\n\t}\n\n\ttd := makeTraces()\n\terr = traceWriter.WriteTraces(context.Background(), td)\n\trequire.NoError(t, err)\n\n\ttdID := td.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0).TraceID()\n\ttraceID, err := model.TraceIDFromBytes(tdID[:])\n\trequire.NoError(t, err)\n\tquery := spanstore.GetTraceParameters{TraceID: traceID}\n\ttrace, err := spanReader.GetTrace(context.Background(), query)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, trace)\n\tassert.Len(t, trace.Spans, 1)\n}\n\nfunc TestWriteTracesError(t *testing.T) {\n\tmockstore := spanstoremocks.NewWriter(t)\n\tmockstore.On(\n\t\t\"WriteSpan\",\n\t\tmock.AnythingOfType(\"context.backgroundCtx\"),\n\t\tmock.AnythingOfType(\"*model.Span\"),\n\t).Return(errors.New(\"mocked error\"))\n\n\ttraceWriter := &TraceWriter{\n\t\tspanWriter: mockstore,\n\t}\n\n\terr := traceWriter.WriteTraces(context.Background(), makeTraces())\n\trequire.ErrorContains(t, err, \"mocked error\")\n}\n\nfunc TestGetV1Writer(t *testing.T) {\n\tt.Run(\"wrapped v1 writer\", func(t *testing.T) {\n\t\twriter := new(spanstoremocks.Writer)\n\t\ttraceWriter := &TraceWriter{\n\t\t\tspanWriter: writer,\n\t\t}\n\t\tv1Writer := GetV1Writer(traceWriter)\n\t\trequire.Equal(t, writer, v1Writer)\n\t})\n\n\tt.Run(\"native v2 writer\", func(t *testing.T) {\n\t\twriter := new(tracestoremocks.Writer)\n\t\tv1Writer := GetV1Writer(writer)\n\t\trequire.IsType(t, &SpanWriter{}, v1Writer)\n\t\trequire.Equal(t, writer, v1Writer.(*SpanWriter).traceWriter)\n\t})\n}\n\nfunc makeTraces() ptrace.Traces {\n\ttraces := ptrace.NewTraces()\n\trSpans := traces.ResourceSpans().AppendEmpty()\n\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\tspan := sSpans.Spans().AppendEmpty()\n\n\tspanID := pcommon.NewSpanIDEmpty()\n\tspanID[5] = 5 // 0000000000050000\n\tspan.SetSpanID(spanID)\n\n\ttraceID := pcommon.NewTraceIDEmpty()\n\ttraceID[15] = 1 // 00000000000000000000000000000001\n\tspan.SetTraceID(traceID)\n\n\treturn traces\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/translator.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"iter\"\n\n\tjaegertranslator \"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\n// V1BatchesFromTraces converts OpenTelemetry traces (ptrace.Traces)\n// to Jaeger model batches ([]*model.Batch).\nfunc V1BatchesFromTraces(traces ptrace.Traces) []*model.Batch {\n\tbatches := jaegertranslator.ProtoFromTraces(traces)\n\tspanMap := createSpanMapFromBatches(batches)\n\ttransferWarningsToModelSpans(traces, spanMap)\n\treturn batches\n}\n\n// V1BatchesToTraces converts Jaeger model batches ([]*model.Batch)\n// to OpenTelemetry traces (ptrace.Traces).\nfunc V1BatchesToTraces(batches []*model.Batch) ptrace.Traces {\n\ttraces, _ := jaegertranslator.ProtoToTraces(batches) // never returns an error\n\tspanMap := jptrace.SpanMap(traces, func(s ptrace.Span) pcommon.SpanID {\n\t\treturn s.SpanID()\n\t})\n\ttransferWarningsToOTLPSpans(batches, spanMap)\n\treturn traces\n}\n\n// V1TracesFromSeq2 converts an interator of ptrace.Traces chunks into v1 traces.\nfunc V1TracesFromSeq2(otelSeq iter.Seq2[[]ptrace.Traces, error]) ([]*model.Trace, error) {\n\tvar (\n\t\tjaegerTraces []*model.Trace\n\t\titerErr      error\n\t)\n\tjptrace.AggregateTraces(otelSeq)(func(otelTrace ptrace.Traces, err error) bool {\n\t\tif err != nil {\n\t\t\titerErr = err\n\t\t\treturn false\n\t\t}\n\t\tjaegerTraces = append(jaegerTraces, modelTraceFromOtelTrace(otelTrace))\n\t\treturn true\n\t})\n\tif iterErr != nil {\n\t\treturn nil, iterErr\n\t}\n\treturn jaegerTraces, nil\n}\n\nfunc V1TraceIDsFromSeq2(traceIDsIter iter.Seq2[[]tracestore.FoundTraceID, error]) ([]model.TraceID, error) {\n\tvar (\n\t\titerErr       error\n\t\tmodelTraceIDs []model.TraceID\n\t)\n\ttraceIDsIter(func(traceIDs []tracestore.FoundTraceID, err error) bool {\n\t\tif err != nil {\n\t\t\titerErr = err\n\t\t\treturn false\n\t\t}\n\t\tfor _, traceID := range traceIDs {\n\t\t\tmodelTraceIDs = append(modelTraceIDs, ToV1TraceID(traceID.TraceID))\n\t\t}\n\t\treturn true\n\t})\n\tif iterErr != nil {\n\t\treturn nil, iterErr\n\t}\n\treturn modelTraceIDs, nil\n}\n\n// V1TraceToOtelTrace converts v1 traces (*model.Trace) to Otel traces (ptrace.Traces)\nfunc V1TraceToOtelTrace(jTrace *model.Trace) ptrace.Traces {\n\tbatches := createBatchesFromModelTrace(jTrace)\n\treturn V1BatchesToTraces(batches)\n}\n\nfunc createBatchesFromModelTrace(jTrace *model.Trace) []*model.Batch {\n\tspans := jTrace.Spans\n\n\tif len(spans) == 0 {\n\t\treturn nil\n\t}\n\tbatch := &model.Batch{\n\t\tSpans: jTrace.Spans,\n\t}\n\treturn []*model.Batch{batch}\n}\n\n// modelTraceFromOtelTrace extracts spans from otel traces\nfunc modelTraceFromOtelTrace(otelTrace ptrace.Traces) *model.Trace {\n\tvar spans []*model.Span\n\tbatches := V1BatchesFromTraces(otelTrace)\n\tfor _, batch := range batches {\n\t\tfor _, span := range batch.Spans {\n\t\t\tif span.Process == nil {\n\t\t\t\tproc := *batch.Process // shallow clone\n\t\t\t\tspan.Process = &proc\n\t\t\t}\n\t\t\tspans = append(spans, span)\n\n\t\t\tif span.Process.Tags == nil {\n\t\t\t\tspan.Process.Tags = []model.KeyValue{}\n\t\t\t}\n\n\t\t\tif span.References == nil {\n\t\t\t\tspan.References = []model.SpanRef{}\n\t\t\t}\n\t\t\tif span.Tags == nil {\n\t\t\t\tspan.Tags = []model.KeyValue{}\n\t\t\t}\n\t\t}\n\t}\n\treturn &model.Trace{Spans: spans}\n}\n\nfunc createSpanMapFromBatches(batches []*model.Batch) map[model.SpanID]*model.Span {\n\tspanMap := make(map[model.SpanID]*model.Span)\n\tfor _, batch := range batches {\n\t\tfor _, span := range batch.Spans {\n\t\t\tspanMap[span.SpanID] = span\n\t\t}\n\t}\n\treturn spanMap\n}\n\nfunc transferWarningsToModelSpans(traces ptrace.Traces, spanMap map[model.SpanID]*model.Span) {\n\tresources := traces.ResourceSpans()\n\tfor i := 0; i < resources.Len(); i++ {\n\t\tscopes := resources.At(i).ScopeSpans()\n\t\tfor j := 0; j < scopes.Len(); j++ {\n\t\t\tspans := scopes.At(j).Spans()\n\t\t\tfor k := 0; k < spans.Len(); k++ {\n\t\t\t\totelSpan := spans.At(k)\n\t\t\t\twarnings := jptrace.GetWarnings(otelSpan)\n\t\t\t\tif len(warnings) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif span, ok := spanMap[ToV1SpanID(otelSpan.SpanID())]; ok {\n\t\t\t\t\tspan.Warnings = append(span.Warnings, warnings...)\n\t\t\t\t\t// filter out the warning tag\n\t\t\t\t\tspan.Tags = filterTags(span.Tags, jptrace.WarningsAttribute)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc transferWarningsToOTLPSpans(batches []*model.Batch, spanMap map[pcommon.SpanID]ptrace.Span) {\n\tfor _, batch := range batches {\n\t\tfor _, span := range batch.Spans {\n\t\t\tif len(span.Warnings) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif otelSpan, ok := spanMap[FromV1SpanID(span.SpanID)]; ok {\n\t\t\t\tjptrace.AddWarnings(otelSpan, span.Warnings...)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc filterTags(tags []model.KeyValue, keyToRemove string) []model.KeyValue {\n\tvar filteredTags []model.KeyValue\n\tfor _, tag := range tags {\n\t\tif tag.Key != keyToRemove {\n\t\t\tfilteredTags = append(filteredTags, tag)\n\t\t}\n\t}\n\treturn filteredTags\n}\n"
  },
  {
    "path": "internal/storage/v2/v1adapter/translator_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage v1adapter\n\nimport (\n\t\"errors\"\n\t\"iter\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/pdata/pcommon\"\n\t\"go.opentelemetry.io/collector/pdata/ptrace\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/jptrace\"\n\t\"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore\"\n)\n\nfunc TestV1BatchesFromTraces_AddsWarnings(t *testing.T) {\n\ttraces := ptrace.NewTraces()\n\trs1 := traces.ResourceSpans().AppendEmpty()\n\tss1 := rs1.ScopeSpans().AppendEmpty()\n\tspan1 := ss1.Spans().AppendEmpty()\n\tspan1.SetName(\"test-span-1\")\n\tspan1.SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))\n\tjptrace.AddWarnings(span1, \"test-warning-1\")\n\tjptrace.AddWarnings(span1, \"test-warning-2\")\n\tspan1.Attributes().PutStr(\"key\", \"value\")\n\n\tss2 := rs1.ScopeSpans().AppendEmpty()\n\tspan2 := ss2.Spans().AppendEmpty()\n\tspan2.SetName(\"test-span-2\")\n\tspan2.SetSpanID(pcommon.SpanID([8]byte{9, 10, 11, 12, 13, 14, 15, 16}))\n\n\trs2 := traces.ResourceSpans().AppendEmpty()\n\tss3 := rs2.ScopeSpans().AppendEmpty()\n\tspan3 := ss3.Spans().AppendEmpty()\n\tspan3.SetName(\"test-span-3\")\n\tspan3.SetSpanID(pcommon.SpanID([8]byte{17, 18, 19, 20, 21, 22, 23, 24}))\n\tjptrace.AddWarnings(span3, \"test-warning-3\")\n\n\tbatches := V1BatchesFromTraces(traces)\n\n\tassert.Len(t, batches, 2)\n\n\tassert.Len(t, batches[0].Spans, 2)\n\tassert.Equal(t, \"test-span-1\", batches[0].Spans[0].OperationName)\n\tassert.Equal(t, []string{\"test-warning-1\", \"test-warning-2\"}, batches[0].Spans[0].Warnings)\n\tassert.Equal(t, []model.KeyValue{{Key: \"key\", VStr: \"value\"}}, batches[0].Spans[0].Tags)\n\tassert.Equal(t, \"test-span-2\", batches[0].Spans[1].OperationName)\n\tassert.Empty(t, batches[0].Spans[1].Warnings)\n\tassert.Empty(t, batches[0].Spans[1].Tags)\n\n\tassert.Len(t, batches[1].Spans, 1)\n\tassert.Equal(t, \"test-span-3\", batches[1].Spans[0].OperationName)\n\tassert.Equal(t, []string{\"test-warning-3\"}, batches[1].Spans[0].Warnings)\n\tassert.Empty(t, batches[1].Spans[0].Tags)\n}\n\nfunc TestProtoToTraces_AddsWarnings(t *testing.T) {\n\tbatch1 := &model.Batch{\n\t\tProcess: &model.Process{\n\t\t\tServiceName: \"batch-1\",\n\t\t},\n\t\tSpans: []*model.Span{\n\t\t\t{\n\t\t\t\tOperationName: \"test-span-1\",\n\t\t\t\tSpanID:        model.NewSpanID(1),\n\t\t\t\tWarnings:      []string{\"test-warning-1\", \"test-warning-2\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tOperationName: \"test-span-2\",\n\t\t\t\tSpanID:        model.NewSpanID(2),\n\t\t\t},\n\t\t},\n\t}\n\tbatch2 := &model.Batch{\n\t\tProcess: &model.Process{\n\t\t\tServiceName: \"batch-2\",\n\t\t},\n\t\tSpans: []*model.Span{\n\t\t\t{\n\t\t\t\tOperationName: \"test-span-3\",\n\t\t\t\tSpanID:        model.NewSpanID(3),\n\t\t\t\tWarnings:      []string{\"test-warning-3\"},\n\t\t\t},\n\t\t},\n\t}\n\tbatches := []*model.Batch{batch1, batch2}\n\ttraces := V1BatchesToTraces(batches)\n\n\tassert.Equal(t, 2, traces.ResourceSpans().Len())\n\n\tspanMap := jptrace.SpanMap(traces, func(s ptrace.Span) string {\n\t\treturn s.Name()\n\t})\n\n\tspan1 := spanMap[\"test-span-1\"]\n\tassert.Equal(t, pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), span1.SpanID())\n\tassert.Equal(t, []string{\"test-warning-1\", \"test-warning-2\"}, jptrace.GetWarnings(span1))\n\n\tspan2 := spanMap[\"test-span-2\"]\n\tassert.Equal(t, pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), span2.SpanID())\n\tassert.Empty(t, jptrace.GetWarnings(span2))\n\n\tspan3 := spanMap[\"test-span-3\"]\n\tassert.Equal(t, pcommon.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 3}), span3.SpanID())\n\tassert.Equal(t, []string{\"test-warning-3\"}, jptrace.GetWarnings(span3))\n}\n\nfunc TestV1TracesFromSeq2(t *testing.T) {\n\tvar (\n\t\tprocessNoServiceName = \"OTLPResourceNoServiceName\"\n\t\tstartTime            = time.Unix(0, 0) // 1970-01-01T00:00:00Z, matches the default for otel span's start time\n\t)\n\n\ttestCases := []struct {\n\t\tname                string\n\t\texpectedModelTraces []*model.Trace\n\t\tseqTrace            iter.Seq2[[]ptrace.Traces, error]\n\t\texpectedErr         error\n\t}{\n\t\t{\n\t\t\tname: \"sequence with one trace\",\n\t\t\texpectedModelTraces: []*model.Trace{\n\t\t\t\t{\n\t\t\t\t\tSpans: []*model.Span{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\t\t\t\tSpanID:        model.NewSpanID(1),\n\t\t\t\t\t\t\tOperationName: \"op-success-a\",\n\t\t\t\t\t\t\tProcess:       model.NewProcess(processNoServiceName, make([]model.KeyValue, 0)),\n\t\t\t\t\t\t\tStartTime:     startTime,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tseqTrace: func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\ttestTrace := ptrace.NewTraces()\n\t\t\t\trSpans := testTrace.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspans := sSpans.Spans()\n\n\t\t\t\t// Add a new span and set attributes\n\t\t\t\tmodelTraceID := model.NewTraceID(2, 3)\n\t\t\t\tspan1 := spans.AppendEmpty()\n\t\t\t\tspan1.SetTraceID(FromV1TraceID(modelTraceID))\n\t\t\t\tspan1.SetName(\"op-success-a\")\n\t\t\t\tspan1.SetSpanID(FromV1SpanID(model.NewSpanID(1)))\n\n\t\t\t\t// Yield the test trace\n\t\t\t\tyield([]ptrace.Traces{testTrace}, nil)\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"sequence with two chunks of a trace\",\n\t\t\texpectedModelTraces: []*model.Trace{\n\t\t\t\t{\n\t\t\t\t\tSpans: []*model.Span{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\t\t\t\tSpanID:        model.NewSpanID(1),\n\t\t\t\t\t\t\tOperationName: \"op-two-chunks-a\",\n\t\t\t\t\t\t\tProcess:       model.NewProcess(processNoServiceName, make([]model.KeyValue, 0)),\n\t\t\t\t\t\t\tStartTime:     startTime,\n\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\t\t\t\tSpanID:        model.NewSpanID(2),\n\t\t\t\t\t\t\tOperationName: \"op-two-chunks-b\",\n\t\t\t\t\t\t\tProcess:       model.NewProcess(processNoServiceName, make([]model.KeyValue, 0)),\n\t\t\t\t\t\t\tStartTime:     startTime,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tseqTrace: func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\ttraceChunk1 := ptrace.NewTraces()\n\t\t\t\trSpans1 := traceChunk1.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans1 := rSpans1.ScopeSpans().AppendEmpty()\n\t\t\t\tspans1 := sSpans1.Spans()\n\t\t\t\tmodelTraceID := model.NewTraceID(2, 3)\n\t\t\t\tspan1 := spans1.AppendEmpty()\n\t\t\t\tspan1.SetTraceID(FromV1TraceID(modelTraceID))\n\t\t\t\tspan1.SetName(\"op-two-chunks-a\")\n\t\t\t\tspan1.SetSpanID(FromV1SpanID(model.NewSpanID(1)))\n\n\t\t\t\ttraceChunk2 := ptrace.NewTraces()\n\t\t\t\trSpans2 := traceChunk2.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans2 := rSpans2.ScopeSpans().AppendEmpty()\n\t\t\t\tspans2 := sSpans2.Spans()\n\t\t\t\tspan2 := spans2.AppendEmpty()\n\t\t\t\tspan2.SetTraceID(FromV1TraceID(modelTraceID))\n\t\t\t\tspan2.SetName(\"op-two-chunks-b\")\n\t\t\t\tspan2.SetSpanID(FromV1SpanID(model.NewSpanID(2)))\n\t\t\t\t// Yield the test trace\n\t\t\t\tyield([]ptrace.Traces{traceChunk1, traceChunk2}, nil)\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\t// a case that occurs when no trace is contained in the iterator\n\t\t\tname:                \"empty sequence\",\n\t\t\texpectedModelTraces: nil,\n\t\t\tseqTrace:            func(_ func([]ptrace.Traces, error) bool) {},\n\t\t\texpectedErr:         nil,\n\t\t},\n\t\t{\n\t\t\tname:                \"sequence containing error\",\n\t\t\texpectedModelTraces: nil,\n\t\t\tseqTrace: func(yield func([]ptrace.Traces, error) bool) {\n\t\t\t\ttestTrace := ptrace.NewTraces()\n\t\t\t\trSpans := testTrace.ResourceSpans().AppendEmpty()\n\t\t\t\tsSpans := rSpans.ScopeSpans().AppendEmpty()\n\t\t\t\tspans := sSpans.Spans()\n\n\t\t\t\tmodelTraceID := model.NewTraceID(2, 3)\n\t\t\t\tspan1 := spans.AppendEmpty()\n\t\t\t\tspan1.SetTraceID(FromV1TraceID(modelTraceID))\n\t\t\t\tspan1.SetName(\"op-error-a\")\n\t\t\t\tspan1.SetSpanID(FromV1SpanID(model.NewSpanID(1)))\n\n\t\t\t\t// Yield the test trace\n\t\t\t\tif !yield([]ptrace.Traces{testTrace}, nil) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tyield(nil, errors.New(\"unexpected-op-err\"))\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"unexpected-op-err\"),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactualTraces, err := V1TracesFromSeq2(tc.seqTrace)\n\t\t\trequire.Equal(t, tc.expectedErr, err)\n\t\t\trequire.Len(t, actualTraces, len(tc.expectedModelTraces))\n\t\t\tif len(tc.expectedModelTraces) < 1 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i, etrace := range tc.expectedModelTraces {\n\t\t\t\teSpans := etrace.Spans\n\t\t\t\taSpans := actualTraces[i].Spans\n\t\t\t\trequire.Len(t, aSpans, len(eSpans))\n\t\t\t\tfor j, espan := range eSpans {\n\t\t\t\t\tassert.Equal(t, espan.TraceID, aSpans[j].TraceID)\n\t\t\t\t\tassert.Equal(t, espan.OperationName, aSpans[j].OperationName)\n\t\t\t\t\tassert.Equal(t, espan.Process, aSpans[j].Process)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestV1TraceToOtelTrace_ReturnsExptectedOtelTrace(t *testing.T) {\n\tjTrace := &model.Trace{\n\t\tSpans: []*model.Span{\n\t\t\t{\n\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\tSpanID:        model.NewSpanID(1),\n\t\t\t\tProcess:       model.NewProcess(\"Service1\", nil),\n\t\t\t\tOperationName: \"two-resources-1\",\n\t\t\t}, {\n\t\t\t\tTraceID:       model.NewTraceID(2, 3),\n\t\t\t\tSpanID:        model.NewSpanID(2),\n\t\t\t\tProcess:       model.NewProcess(\"service2\", nil),\n\t\t\t\tOperationName: \"two-resources-2\",\n\t\t\t},\n\t\t},\n\t}\n\tactualTrace := V1TraceToOtelTrace(jTrace)\n\n\trequire.NotEmpty(t, actualTrace)\n\trequire.Equal(t, 2, actualTrace.ResourceSpans().Len())\n}\n\nfunc TestV1TraceToOtelTrace_ReturnEmptyOtelTrace(t *testing.T) {\n\tjTrace := &model.Trace{}\n\teTrace := ptrace.NewTraces()\n\taTrace := V1TraceToOtelTrace(jTrace)\n\n\trequire.Equal(t, eTrace.SpanCount(), aTrace.SpanCount(), 0)\n}\n\nfunc TestV1TraceIDsFromSeq2(t *testing.T) {\n\ttestCases := []struct {\n\t\tname          string\n\t\tseqTraceIDs   iter.Seq2[[]tracestore.FoundTraceID, error]\n\t\texpectedIDs   []model.TraceID\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname:          \"empty sequence\",\n\t\t\tseqTraceIDs:   func(func([]tracestore.FoundTraceID, error) bool) {},\n\t\t\texpectedIDs:   nil,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"sequence with error\",\n\t\t\tseqTraceIDs: func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\t\t\tyield(nil, assert.AnError)\n\t\t\t},\n\t\t\texpectedIDs:   nil,\n\t\t\texpectedError: assert.AnError,\n\t\t},\n\t\t{\n\t\t\tname: \"sequence with one chunk of trace IDs\",\n\t\t\tseqTraceIDs: func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\t\t\tyield([]tracestore.FoundTraceID{\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTraceID: pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5}),\n\t\t\t\t\t},\n\t\t\t\t}, nil)\n\t\t\t},\n\t\t\texpectedIDs: []model.TraceID{\n\t\t\t\tmodel.NewTraceID(2, 3),\n\t\t\t\tmodel.NewTraceID(4, 5),\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"sequence with multiple chunks of trace IDs\",\n\t\t\tseqTraceIDs: func(yield func([]tracestore.FoundTraceID, error) bool) {\n\t\t\t\ttraceID1 := pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3})\n\t\t\t\ttraceID2 := pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5})\n\t\t\t\ttraceID3 := pcommon.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7})\n\t\t\t\tyield([]tracestore.FoundTraceID{{TraceID: traceID1}}, nil)\n\t\t\t\tyield([]tracestore.FoundTraceID{{TraceID: traceID2}, {TraceID: traceID3}}, nil)\n\t\t\t},\n\t\t\texpectedIDs: []model.TraceID{\n\t\t\t\tmodel.NewTraceID(2, 3),\n\t\t\t\tmodel.NewTraceID(4, 5),\n\t\t\t\tmodel.NewTraceID(6, 7),\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactualIDs, err := V1TraceIDsFromSeq2(tc.seqTraceIDs)\n\t\t\trequire.Equal(t, tc.expectedError, err)\n\t\t\trequire.Equal(t, tc.expectedIDs, actualIDs)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/telemetry/otelsemconv/empty_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelsemconv\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/telemetry/otelsemconv/semconv.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelsemconv\n\nimport (\n\t\"go.opentelemetry.io/otel/attribute\"\n\tsemconv \"go.opentelemetry.io/otel/semconv/v1.40.0\"\n)\n\n// We do not use a lot of semconv constants, and its annoying to keep\n// the semver of the imports the same. This package serves as a\n// one stop shop replacement / alias.\nconst (\n\tSchemaURL = semconv.SchemaURL\n\n\t// Telemetry SDK\n\tTelemetrySDKLanguageKey   = string(semconv.TelemetrySDKLanguageKey)\n\tTelemetrySDKNameKey       = string(semconv.TelemetrySDKNameKey)\n\tTelemetrySDKVersionKey    = string(semconv.TelemetrySDKVersionKey)\n\tTelemetryDistroNameKey    = string(semconv.TelemetryDistroNameKey)\n\tTelemetryDistroVersionKey = string(semconv.TelemetryDistroVersionKey)\n\n\t// Service\n\tServiceNameKey = string(semconv.ServiceNameKey)\n\n\t// Database\n\tDBQueryTextKey = string(semconv.DBQueryTextKey)\n\tDBSystemKey    = \"db.system\"\n\n\t// Network\n\tPeerServiceKey = string(semconv.ServicePeerNameKey)\n\n\t// HTTP\n\tHTTPResponseStatusCodeKey = string(semconv.HTTPResponseStatusCodeKey)\n\n\t// Host\n\tHostIDKey   = string(semconv.HostIDKey)\n\tHostIPKey   = string(semconv.HostIPKey)\n\tHostNameKey = string(semconv.HostNameKey)\n\n\t// Status\n\tOtelStatusCode        = \"otel.status_code\"\n\tOtelStatusDescription = \"otel.status_description\"\n\n\t// OpenTracing\n\tAttributeOpentracingRefType            = \"opentracing.ref_type\"\n\tAttributeOpentracingRefTypeChildOf     = \"child_of\"\n\tAttributeOpentracingRefTypeFollowsFrom = \"follows_from\"\n\n\t// OTel Scope\n\tAttributeOtelScopeName    = \"otel.scope.name\"\n\tAttributeOtelScopeVersion = \"otel.scope.version\"\n)\n\n// Helper functions for creating typed attributes for the OpenTelemetry SDK.\n// ServiceName creates a key-value pair for the service name attribute.\nfunc ServiceNameAttribute(value string) attribute.KeyValue {\n\treturn semconv.ServiceNameKey.String(value)\n}\n\n// PeerService creates a key-value pair for the peer service attribute.\nfunc PeerServiceAttribute(value string) attribute.KeyValue {\n\treturn semconv.ServicePeerNameKey.String(value)\n}\n\n// DBSystem creates a key-value pair for the DB system attribute.\nfunc DBSystemAttribute(value string) attribute.KeyValue {\n\treturn semconv.DBSystemNameKey.String(value)\n}\n\n// HTTPStatusCode creates a key-value pair for the HTTP status code attribute.\nfunc HTTPStatusCodeAttribute(value int) attribute.KeyValue {\n\treturn semconv.HTTPResponseStatusCodeKey.Int(value)\n}\n\n// This var provides the original semconv function variable for creating an int attribute.\nvar HTTPResponseStatusCode = semconv.HTTPResponseStatusCode\n"
  },
  {
    "path": "internal/telemetry/otelsemconv/semconv_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage otelsemconv\n\nimport (\n\t\"testing\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\tsemconv \"go.opentelemetry.io/otel/semconv/v1.40.0\"\n)\n\nfunc TestServiceNameAttribute(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tvalue    string\n\t\texpected attribute.KeyValue\n\t}{\n\t\t{\n\t\t\tname:     \"valid service name\",\n\t\t\tvalue:    \"my-service\",\n\t\t\texpected: semconv.ServiceNameKey.String(\"my-service\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"empty service name\",\n\t\t\tvalue:    \"\",\n\t\t\texpected: semconv.ServiceNameKey.String(\"\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"service name with spaces\",\n\t\t\tvalue:    \"my service name\",\n\t\t\texpected: semconv.ServiceNameKey.String(\"my service name\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := ServiceNameAttribute(tt.value)\n\t\t\tif result.Key != tt.expected.Key {\n\t\t\t\tt.Errorf(\"Expected key %v, got %v\", tt.expected.Key, result.Key)\n\t\t\t}\n\t\t\tif result.Value != tt.expected.Value {\n\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expected.Value, result.Value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPeerServiceAttribute(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tvalue    string\n\t\texpected attribute.KeyValue\n\t}{\n\t\t{\n\t\t\tname:     \"valid peer service\",\n\t\t\tvalue:    \"external-api\",\n\t\t\texpected: semconv.ServicePeerNameKey.String(\"external-api\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"empty peer service\",\n\t\t\tvalue:    \"\",\n\t\t\texpected: semconv.ServicePeerNameKey.String(\"\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"peer service with special characters\",\n\t\t\tvalue:    \"api-service_v1.2\",\n\t\t\texpected: semconv.ServicePeerNameKey.String(\"api-service_v1.2\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := PeerServiceAttribute(tt.value)\n\t\t\tif result.Key != tt.expected.Key {\n\t\t\t\tt.Errorf(\"Expected key %v, got %v\", tt.expected.Key, result.Key)\n\t\t\t}\n\t\t\tif result.Value != tt.expected.Value {\n\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expected.Value, result.Value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDBSystemAttribute(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tvalue    string\n\t\texpected attribute.KeyValue\n\t}{\n\t\t{\n\t\t\tname:     \"postgresql database\",\n\t\t\tvalue:    \"postgresql\",\n\t\t\texpected: semconv.DBSystemNameKey.String(\"postgresql\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"mysql database\",\n\t\t\tvalue:    \"mysql\",\n\t\t\texpected: semconv.DBSystemNameKey.String(\"mysql\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"empty database system\",\n\t\t\tvalue:    \"\",\n\t\t\texpected: semconv.DBSystemNameKey.String(\"\"),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := DBSystemAttribute(tt.value)\n\t\t\tif result.Key != tt.expected.Key {\n\t\t\t\tt.Errorf(\"Expected key %v, got %v\", tt.expected.Key, result.Key)\n\t\t\t}\n\t\t\tif result.Value != tt.expected.Value {\n\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expected.Value, result.Value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHTTPStatusCodeAttribute(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tvalue    int\n\t\texpected attribute.KeyValue\n\t}{\n\t\t{\n\t\t\tname:     \"success status code\",\n\t\t\tvalue:    200,\n\t\t\texpected: semconv.HTTPResponseStatusCodeKey.Int(200),\n\t\t},\n\t\t{\n\t\t\tname:     \"client error status code\",\n\t\t\tvalue:    404,\n\t\t\texpected: semconv.HTTPResponseStatusCodeKey.Int(404),\n\t\t},\n\t\t{\n\t\t\tname:     \"server error status code\",\n\t\t\tvalue:    500,\n\t\t\texpected: semconv.HTTPResponseStatusCodeKey.Int(500),\n\t\t},\n\t\t{\n\t\t\tname:     \"zero status code\",\n\t\t\tvalue:    0,\n\t\t\texpected: semconv.HTTPResponseStatusCodeKey.Int(0),\n\t\t},\n\t\t{\n\t\t\tname:     \"negative status code\",\n\t\t\tvalue:    -1,\n\t\t\texpected: semconv.HTTPResponseStatusCodeKey.Int(-1),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := HTTPStatusCodeAttribute(tt.value)\n\t\t\tif result.Key != tt.expected.Key {\n\t\t\t\tt.Errorf(\"Expected key %v, got %v\", tt.expected.Key, result.Key)\n\t\t\t}\n\t\t\tif result.Value != tt.expected.Value {\n\t\t\t\tt.Errorf(\"Expected value %v, got %v\", tt.expected.Value, result.Value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAttributeTypes(t *testing.T) {\n\t// Test that all helper functions return the correct attribute types\n\tserviceAttr := ServiceNameAttribute(\"test\")\n\tif serviceAttr.Value.Type() != attribute.STRING {\n\t\tt.Errorf(\"ServiceNameAttribute should return STRING type, got %v\", serviceAttr.Value.Type())\n\t}\n\n\tpeerAttr := PeerServiceAttribute(\"test\")\n\tif peerAttr.Value.Type() != attribute.STRING {\n\t\tt.Errorf(\"PeerServiceAttribute should return STRING type, got %v\", peerAttr.Value.Type())\n\t}\n\n\tdbAttr := DBSystemAttribute(\"test\")\n\tif dbAttr.Value.Type() != attribute.STRING {\n\t\tt.Errorf(\"DBSystemAttribute should return STRING type, got %v\", dbAttr.Value.Type())\n\t}\n\n\thttpAttr := HTTPStatusCodeAttribute(200)\n\tif httpAttr.Value.Type() != attribute.INT64 {\n\t\tt.Errorf(\"HTTPStatusCodeAttribute should return INT64 type, got %v\", httpAttr.Value.Type())\n\t}\n}\n"
  },
  {
    "path": "internal/telemetry/settings.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage telemetry\n\nimport (\n\t\"go.opentelemetry.io/collector/component\"\n\t\"go.opentelemetry.io/collector/component/componentstatus\"\n\t\"go.opentelemetry.io/collector/component/componenttest\"\n\t\"go.opentelemetry.io/otel/metric\"\n\tnoopmetric \"go.opentelemetry.io/otel/metric/noop\"\n\t\"go.opentelemetry.io/otel/trace\"\n\tnooptrace \"go.opentelemetry.io/otel/trace/noop\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n\t\"github.com/jaegertracing/jaeger/internal/metrics/otelmetrics\"\n)\n\ntype Settings struct {\n\tLogger         *zap.Logger\n\tMetrics        metrics.Factory\n\tMeterProvider  metric.MeterProvider\n\tTracerProvider trace.TracerProvider\n\tHost           component.Host\n}\n\n// ReportStatus reports a component status event.\n// If Host is set, it delegates to componentstatus.ReportStatus.\n// Otherwise, it logs the status as an info message.\nfunc (s Settings) ReportStatus(event *componentstatus.Event) {\n\tif s.Host != nil {\n\t\tcomponentstatus.ReportStatus(s.Host, event)\n\t} else if s.Logger != nil {\n\t\ts.Logger.Info(\"status\", zap.Stringer(\"status\", event.Status()))\n\t}\n}\n\nfunc NoopSettings() Settings {\n\treturn Settings{\n\t\tLogger:         zap.NewNop(),\n\t\tMetrics:        metrics.NullFactory,\n\t\tMeterProvider:  noopmetric.NewMeterProvider(),\n\t\tTracerProvider: nooptrace.NewTracerProvider(),\n\t\tHost:           componenttest.NewNopHost(),\n\t}\n}\n\nfunc FromOtelComponent(telset component.TelemetrySettings, host component.Host) Settings {\n\treturn Settings{\n\t\tLogger:         telset.Logger,\n\t\tMetrics:        otelmetrics.NewFactory(telset.MeterProvider),\n\t\tMeterProvider:  telset.MeterProvider,\n\t\tTracerProvider: telset.TracerProvider,\n\t\tHost:           host,\n\t}\n}\n\nfunc (s Settings) ToOtelComponent() component.TelemetrySettings {\n\treturn component.TelemetrySettings{\n\t\tLogger:         s.Logger,\n\t\tMeterProvider:  s.MeterProvider,\n\t\tTracerProvider: s.TracerProvider,\n\t}\n}\n"
  },
  {
    "path": "internal/telemetry/settings_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage telemetry_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/collector/component\"\n\t\"go.opentelemetry.io/collector/component/componentstatus\"\n\t\"go.opentelemetry.io/collector/component/componenttest\"\n\tnoopmetric \"go.opentelemetry.io/otel/metric/noop\"\n\tnooptrace \"go.opentelemetry.io/otel/trace/noop\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/jaegertracing/jaeger/internal/telemetry\"\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestNoopSettings(t *testing.T) {\n\ttelset := telemetry.NoopSettings()\n\tassert.NotNil(t, telset.Logger)\n\tassert.NotNil(t, telset.Metrics)\n\tassert.NotNil(t, telset.MeterProvider)\n\tassert.NotNil(t, telset.TracerProvider)\n\tassert.NotNil(t, telset.Host)\n\t// ReportStatus is now a method, not a field - just verify it doesn't panic\n\ttelset.ReportStatus(componentstatus.NewFatalErrorEvent(errors.New(\"foobar\")))\n}\n\nfunc TestFromOtelComponent(t *testing.T) {\n\totelTelset := component.TelemetrySettings{\n\t\tLogger:         zap.NewNop(),\n\t\tMeterProvider:  noopmetric.NewMeterProvider(),\n\t\tTracerProvider: nooptrace.NewTracerProvider(),\n\t}\n\thost := componenttest.NewNopHost()\n\ttelset := telemetry.FromOtelComponent(otelTelset, host)\n\tassert.Equal(t, otelTelset.Logger, telset.Logger)\n\tassert.Equal(t, otelTelset.MeterProvider, telset.MeterProvider)\n\tassert.Equal(t, otelTelset.TracerProvider, telset.TracerProvider)\n\tassert.Equal(t, host, telset.Host)\n\t// ReportStatus is now a method - just verify it doesn't panic\n\ttelset.ReportStatus(componentstatus.NewFatalErrorEvent(errors.New(\"foobar\")))\n}\n\nfunc TestReportStatus_NilHost(_ *testing.T) {\n\ttelset := telemetry.Settings{\n\t\tLogger: zap.NewNop(),\n\t}\n\t// Should not panic, just log\n\ttelset.ReportStatus(componentstatus.NewEvent(componentstatus.StatusOK))\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/tenancy/context.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport \"context\"\n\n// tenantKeyType is a custom type for the key \"tenant\", following context.Context convention\ntype tenantKeyType string\n\nconst (\n\t// tenantKey holds tenancy for spans\n\ttenantKey = tenantKeyType(\"tenant\")\n)\n\n// WithTenant creates a Context with a tenant association\nfunc WithTenant(ctx context.Context, tenant string) context.Context {\n\treturn context.WithValue(ctx, tenantKey, tenant)\n}\n\n// GetTenant retrieves a tenant associated with a Context\nfunc GetTenant(ctx context.Context) string {\n\ttenant := ctx.Value(tenantKey)\n\tif tenant == nil {\n\t\treturn \"\"\n\t}\n\n\tif s, ok := tenant.(string); ok {\n\t\treturn s\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "internal/tenancy/context_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype testContextKey string\n\nfunc TestContextTenantHandling(t *testing.T) {\n\tctxWithTenant := WithTenant(context.Background(), \"tenant1\")\n\tassert.Equal(t, \"tenant1\", GetTenant(ctxWithTenant))\n}\n\nfunc TestContextPreserved(t *testing.T) {\n\tkey := testContextKey(\"expected-key\")\n\tval := \"expected-value\"\n\tctxWithValue := context.WithValue(context.Background(), key, val)\n\tctxWithTenant := WithTenant(ctxWithValue, \"tenant1\")\n\tassert.Equal(t, \"tenant1\", GetTenant(ctxWithTenant))\n\tassert.Equal(t, val, ctxWithTenant.Value(key))\n}\n\nfunc TestNoTenant(t *testing.T) {\n\t// If no tenant in context, GetTenant should return the empty string\n\tassert.Empty(t, GetTenant(context.Background()))\n}\n\nfunc TestImpossibleTenantType(t *testing.T) {\n\t// If the tenant is not a string, GetTenant should return the empty string\n\tctxWithIntTenant := context.WithValue(context.Background(), tenantKey, -1)\n\tassert.Empty(t, GetTenant(ctxWithIntTenant))\n}\n"
  },
  {
    "path": "internal/tenancy/flags.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/viper\"\n)\n\nconst (\n\tflagPrefix         = \"multi-tenancy\"\n\tflagTenancyEnabled = flagPrefix + \".enabled\"\n\tflagTenancyHeader  = flagPrefix + \".header\"\n\tflagValidTenants   = flagPrefix + \".tenants\"\n)\n\n// AddFlags adds flags for tenancy to the FlagSet.\nfunc AddFlags(flags *flag.FlagSet) {\n\tflags.Bool(flagTenancyEnabled, false, \"Enable tenancy header when receiving or querying\")\n\tflags.String(flagTenancyHeader, \"x-tenant\", \"HTTP header carrying tenant\")\n\tflags.String(flagValidTenants, \"\",\n\t\tfmt.Sprintf(\"comma-separated list of allowed values for --%s header.  (If not supplied, tenants are not restricted)\",\n\t\t\tflagTenancyHeader))\n}\n\n// InitFromViper creates tenancy.Options populated with values retrieved from Viper.\nfunc InitFromViper(v *viper.Viper) Options {\n\tvar p Options\n\tp.Enabled = v.GetBool(flagTenancyEnabled)\n\tp.Header = v.GetString(flagTenancyHeader)\n\ttenants := v.GetString(flagValidTenants)\n\tif tenants != \"\" {\n\t\tp.Tenants = strings.Split(tenants, \",\")\n\t} else {\n\t\tp.Tenants = []string{}\n\t}\n\n\treturn p\n}\n"
  },
  {
    "path": "internal/tenancy/flags_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTenancyFlags(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcmd      []string\n\t\texpected Options\n\t}{\n\t\t{\n\t\t\tname: \"one tenant\",\n\t\t\tcmd: []string{\n\t\t\t\t\"--multi-tenancy.enabled=true\",\n\t\t\t\t\"--multi-tenancy.tenants=acme\",\n\t\t\t},\n\t\t\texpected: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{\"acme\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"two tenants\",\n\t\t\tcmd: []string{\n\t\t\t\t\"--multi-tenancy.enabled=true\",\n\t\t\t\t\"--multi-tenancy.tenants=acme,country-store\",\n\t\t\t},\n\t\t\texpected: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{\"acme\", \"country-store\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom header\",\n\t\t\tcmd: []string{\n\t\t\t\t\"--multi-tenancy.enabled=true\",\n\t\t\t\t\"--multi-tenancy.header=jaeger-tenant\",\n\t\t\t\t\"--multi-tenancy.tenants=acme\",\n\t\t\t},\n\t\t\texpected: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"jaeger-tenant\",\n\t\t\t\tTenants: []string{\"acme\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// Not supplying a list of tenants will mean\n\t\t\t// \"tenant header required, but any value will pass\"\n\t\t\tname: \"no_tenants\",\n\t\t\tcmd: []string{\n\t\t\t\t\"--multi-tenancy.enabled=true\",\n\t\t\t},\n\t\t\texpected: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tv := viper.New()\n\t\t\tcommand := cobra.Command{}\n\t\t\tflagSet := &flag.FlagSet{}\n\t\t\tAddFlags(flagSet)\n\t\t\tcommand.PersistentFlags().AddGoFlagSet(flagSet)\n\t\t\tv.BindPFlags(command.PersistentFlags())\n\n\t\t\terr := command.ParseFlags(test.cmd)\n\t\t\trequire.NoError(t, err)\n\t\t\ttenancyCfg := InitFromViper(v)\n\t\t\tassert.Equal(t, test.expected, tenancyCfg)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/tenancy/grpc.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"context\"\n\n\t\"go.opentelemetry.io/collector/client\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// tenantedServerStream is a wrapper for ServerStream providing settable context\ntype tenantedServerStream struct {\n\tgrpc.ServerStream\n\tcontext context.Context\n}\n\nfunc (tss *tenantedServerStream) Context() context.Context {\n\treturn tss.context\n}\n\nfunc GetValidTenant(ctx context.Context, tm *Manager) (string, error) {\n\ttenant, err := extractTenantFromSources(ctx, tm.Header)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif !tm.Valid(tenant) {\n\t\treturn \"\", status.Errorf(codes.PermissionDenied, \"unknown tenant\")\n\t}\n\n\treturn tenant, nil\n}\n\n// helper function to extract tenant from different sources\nfunc extractTenantFromSources(ctx context.Context, header string) (string, error) {\n\tif tenant := GetTenant(ctx); tenant != \"\" {\n\t\treturn tenant, nil\n\t}\n\n\tif cli := client.FromContext(ctx); cli.Metadata.Get(header) != nil {\n\t\tif tenants := cli.Metadata.Get(header); len(tenants) > 0 {\n\t\t\treturn extractSingleTenant(tenants)\n\t\t}\n\t}\n\n\tmd, ok := metadata.FromIncomingContext(ctx)\n\tif !ok {\n\t\treturn \"\", status.Errorf(codes.PermissionDenied, \"missing tenant header\")\n\t}\n\n\treturn extractSingleTenant(md.Get(header))\n}\n\n// Helper function for metadata extraction\nfunc tenantFromMetadata(md metadata.MD, header string) (string, error) {\n\ttenants := md.Get(header)\n\treturn extractSingleTenant(tenants)\n}\n\n// Ensures single tenant value exists\nfunc extractSingleTenant(tenants []string) (string, error) {\n\tswitch len(tenants) {\n\tcase 0:\n\t\treturn \"\", status.Errorf(codes.Unauthenticated, \"missing tenant header\")\n\tcase 1:\n\t\treturn tenants[0], nil\n\tdefault:\n\t\treturn \"\", status.Errorf(codes.PermissionDenied, \"extra tenant header\")\n\t}\n}\n\nfunc directlyAttachedTenant(ctx context.Context) bool {\n\treturn GetTenant(ctx) != \"\"\n}\n\n// NewGuardingStreamInterceptor blocks handling of streams whose tenancy header doesn't meet tenancy requirements.\n// It also ensures the tenant is directly in the context, rather than context metadata.\nfunc NewGuardingStreamInterceptor(tc *Manager) grpc.StreamServerInterceptor {\n\treturn func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\t\ttenant, err := GetValidTenant(ss.Context(), tc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif directlyAttachedTenant(ss.Context()) {\n\t\t\treturn handler(srv, ss)\n\t\t}\n\n\t\t// \"upgrade\" the tenant to be part of the context, rather than just incoming metadata\n\t\treturn handler(srv, &tenantedServerStream{\n\t\t\tServerStream: ss,\n\t\t\tcontext:      WithTenant(ss.Context(), tenant),\n\t\t})\n\t}\n}\n\n// NewGuardingUnaryInterceptor blocks handling of RPCs whose tenancy header doesn't meet tenancy requirements.\n// It also ensures the tenant is directly in the context, rather than context metadata.\nfunc NewGuardingUnaryInterceptor(tc *Manager) grpc.UnaryServerInterceptor {\n\treturn func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {\n\t\ttenant, err := GetValidTenant(ctx, tc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif directlyAttachedTenant(ctx) {\n\t\t\treturn handler(ctx, req)\n\t\t}\n\n\t\treturn handler(WithTenant(ctx, tenant), req)\n\t}\n}\n\n// NewClientUnaryInterceptor injects tenant header into gRPC request metadata.\nfunc NewClientUnaryInterceptor(tc *Manager) grpc.UnaryClientInterceptor {\n\treturn grpc.UnaryClientInterceptor(func(\n\t\tctx context.Context,\n\t\tmethod string,\n\t\treq, reply any,\n\t\tcc *grpc.ClientConn,\n\t\tinvoker grpc.UnaryInvoker,\n\t\topts ...grpc.CallOption,\n\t) error {\n\t\tif tenant := GetTenant(ctx); tenant != \"\" {\n\t\t\tctx = metadata.AppendToOutgoingContext(ctx, tc.Header, tenant)\n\t\t}\n\t\treturn invoker(ctx, method, req, reply, cc, opts...)\n\t})\n}\n\n// NewClientStreamInterceptor injects tenant header into gRPC request metadata.\nfunc NewClientStreamInterceptor(tc *Manager) grpc.StreamClientInterceptor {\n\treturn grpc.StreamClientInterceptor(func(\n\t\tctx context.Context,\n\t\tdesc *grpc.StreamDesc,\n\t\tcc *grpc.ClientConn,\n\t\tmethod string,\n\t\tstreamer grpc.Streamer,\n\t\topts ...grpc.CallOption,\n\t) (grpc.ClientStream, error) {\n\t\tif tenant := GetTenant(ctx); tenant != \"\" {\n\t\t\tctx = metadata.AppendToOutgoingContext(ctx, tc.Header, tenant)\n\t\t}\n\t\treturn streamer(ctx, desc, cc, method, opts...)\n\t})\n}\n"
  },
  {
    "path": "internal/tenancy/grpc_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.opentelemetry.io/collector/client\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc TestTenancyInterceptors(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\ttenancyMgr *Manager\n\t\tctx        context.Context\n\t\terrMsg     string\n\t}{\n\t\t{\n\t\t\tname:       \"missing tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true}),\n\t\t\tctx:        context.Background(),\n\t\t\terrMsg:     \"rpc error: code = PermissionDenied desc = missing tenant header\",\n\t\t},\n\t\t{\n\t\t\tname:       \"invalid tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"megacorp\"}}),\n\t\t\tctx:        WithTenant(context.Background(), \"acme\"),\n\t\t\terrMsg:     \"rpc error: code = PermissionDenied desc = unknown tenant\",\n\t\t},\n\t\t{\n\t\t\tname:       \"valid tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}}),\n\t\t\tctx:        WithTenant(context.Background(), \"acme\"),\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"invalid tenant header\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"megacorp\"}}),\n\t\t\tctx:        metadata.NewIncomingContext(context.Background(), map[string][]string{\"x-tenant\": {\"acme\"}}),\n\t\t\terrMsg:     \"rpc error: code = PermissionDenied desc = unknown tenant\",\n\t\t},\n\t\t{\n\t\t\tname:       \"missing tenant header\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"megacorp\"}}),\n\t\t\tctx:        metadata.NewIncomingContext(context.Background(), map[string][]string{}),\n\t\t\terrMsg:     \"rpc error: code = Unauthenticated desc = missing tenant header\",\n\t\t},\n\t\t{\n\t\t\tname:       \"valid tenant header\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}}),\n\t\t\tctx:        metadata.NewIncomingContext(context.Background(), map[string][]string{\"x-tenant\": {\"acme\"}}),\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"extra tenant header\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}}),\n\t\t\tctx:        metadata.NewIncomingContext(context.Background(), map[string][]string{\"x-tenant\": {\"acme\", \"megacorp\"}}),\n\t\t\terrMsg:     \"rpc error: code = PermissionDenied desc = extra tenant header\",\n\t\t},\n\t\t{\n\t\t\tname:       \"missing tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true}),\n\t\t\tctx: client.NewContext(context.Background(), client.Info{\n\t\t\t\tMetadata: client.NewMetadata(map[string][]string{}),\n\t\t\t}),\n\t\t\terrMsg: \"rpc error: code = PermissionDenied desc = missing tenant header\",\n\t\t},\n\t\t{\n\t\t\tname:       \"invalid tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"megacorp\"}}),\n\t\t\tctx: client.NewContext(context.Background(), client.Info{\n\t\t\t\tMetadata: client.NewMetadata(map[string][]string{\"x-tenant\": {\"acme\"}}),\n\t\t\t}),\n\t\t\terrMsg: \"rpc error: code = PermissionDenied desc = unknown tenant\",\n\t\t},\n\t\t{\n\t\t\tname:       \"valid tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}}),\n\t\t\tctx: client.NewContext(context.Background(), client.Info{\n\t\t\t\tMetadata: client.NewMetadata(map[string][]string{\"x-tenant\": {\"acme\"}}),\n\t\t\t}),\n\t\t\terrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"extra tenant context\",\n\t\t\ttenancyMgr: NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}}),\n\t\t\tctx: client.NewContext(context.Background(), client.Info{\n\t\t\t\tMetadata: client.NewMetadata(map[string][]string{\"x-tenant\": {\"acme\", \"megacorp\"}}),\n\t\t\t}),\n\t\t\terrMsg: \"rpc error: code = PermissionDenied desc = extra tenant header\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tinterceptor := NewGuardingStreamInterceptor(test.tenancyMgr)\n\t\t\tss := tenantedServerStream{\n\t\t\t\tcontext: test.ctx,\n\t\t\t}\n\t\t\tssi := grpc.StreamServerInfo{}\n\t\t\thandler := func(any, grpc.ServerStream) error {\n\t\t\t\t// do nothing\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\terr := interceptor(0, &ss, &ssi, handler)\n\t\t\tif test.errMsg == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Equal(t, test.errMsg, err.Error())\n\t\t\t}\n\n\t\t\tuinterceptor := NewGuardingUnaryInterceptor(test.tenancyMgr)\n\t\t\tusi := &grpc.UnaryServerInfo{}\n\t\t\tiface := 0\n\t\t\tuhandler := func(_ context.Context, req any) (any, error) {\n\t\t\t\t// do nothing\n\t\t\t\treturn req, nil\n\t\t\t}\n\t\t\t_, err = uinterceptor(test.ctx, iface, usi, uhandler)\n\t\t\tif test.errMsg == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Equal(t, test.errMsg, err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientUnaryInterceptor(t *testing.T) {\n\ttm := NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}})\n\tinterceptor := NewClientUnaryInterceptor(tm)\n\tvar tenant string\n\tfakeErr := errors.New(\"foo\")\n\tinvoker := func(ctx context.Context, _ /* method */ string, _ /* req */, _ /* reply */ any, _ *grpc.ClientConn, _ ...grpc.CallOption) error {\n\t\tmd, ok := metadata.FromOutgoingContext(ctx)\n\t\tassert.True(t, ok)\n\t\tten, err := tenantFromMetadata(md, tm.Header)\n\t\trequire.NoError(t, err)\n\t\ttenant = ten\n\t\treturn fakeErr\n\t}\n\tctx := WithTenant(context.Background(), \"acme\")\n\terr := interceptor(ctx, \"method\", \"request\", \"response\", nil, invoker)\n\tassert.Equal(t, \"acme\", tenant)\n\tassert.Same(t, fakeErr, err)\n}\n\nfunc TestClientStreamInterceptor(t *testing.T) {\n\ttm := NewManager(&Options{Enabled: true, Tenants: []string{\"acme\"}})\n\tinterceptor := NewClientStreamInterceptor(tm)\n\tvar tenant string\n\tfakeErr := errors.New(\"foo\")\n\tctx := WithTenant(context.Background(), \"acme\")\n\tstreamer := func(ctx context.Context, _ *grpc.StreamDesc, _ *grpc.ClientConn, _ /* method */ string, _ ...grpc.CallOption) (grpc.ClientStream, error) {\n\t\tmd, ok := metadata.FromOutgoingContext(ctx)\n\t\tassert.True(t, ok)\n\t\tten, err := tenantFromMetadata(md, tm.Header)\n\t\trequire.NoError(t, err)\n\t\ttenant = ten\n\t\treturn nil, fakeErr\n\t}\n\tstream, err := interceptor(ctx, &grpc.StreamDesc{}, nil, \"\", streamer)\n\tassert.Same(t, fakeErr, err)\n\trequire.Nil(t, stream)\n\tassert.Equal(t, \"acme\", tenant)\n}\n"
  },
  {
    "path": "internal/tenancy/http.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// PropagationHandler returns a http.Handler containing the logic to extract\n// the tenancy header of the http.Request and insert the tenant into request.Context\n// for propagation. The token can be accessed via tenancy.GetTenant().\nfunc ExtractTenantHTTPHandler(tc *Manager, h http.Handler) http.Handler {\n\tif !tc.Enabled {\n\t\treturn h\n\t}\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttenant := r.Header.Get(tc.Header)\n\t\tif tenant == \"\" {\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t\tw.Write([]byte(\"missing tenant header\"))\n\t\t\treturn\n\t\t}\n\n\t\tif !tc.Valid(tenant) {\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t\tw.Write([]byte(\"unknown tenant\"))\n\t\t\treturn\n\t\t}\n\n\t\tctx := WithTenant(r.Context(), tenant)\n\t\th.ServeHTTP(w, r.WithContext(ctx))\n\t})\n}\n\n// MetadataAnnotator returns a function suitable for propagating tenancy\n// via github.com/grpc-ecosystem/grpc-gateway/runtime.NewServeMux\nfunc (tc *Manager) MetadataAnnotator() func(context.Context, *http.Request) metadata.MD {\n\treturn func(_ context.Context, req *http.Request) metadata.MD {\n\t\ttenant := req.Header.Get(tc.Header)\n\t\tif tenant == \"\" {\n\t\t\t// The HTTP request lacked the tenancy header.  Pass along\n\t\t\t// empty metadata -- the gRPC query service will reject later.\n\t\t\treturn metadata.Pairs()\n\t\t}\n\t\treturn metadata.New(map[string]string{\n\t\t\ttc.Header: tenant,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/tenancy/http_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype testHttpHandler struct {\n\treached bool\n}\n\nfunc (thh *testHttpHandler) ServeHTTP(http.ResponseWriter, *http.Request) {\n\tthh.reached = true\n}\n\nfunc TestProgationHandler(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\ttenancyMgr     *Manager\n\t\tshouldReach    bool\n\t\trequestHeaders map[string][]string\n\t}{\n\t\t{\n\t\t\tname:           \"untenanted\",\n\t\t\ttenancyMgr:     NewManager(&Options{}),\n\t\t\trequestHeaders: map[string][]string{},\n\t\t\tshouldReach:    true,\n\t\t},\n\t\t{\n\t\t\tname:           \"missing tenant header\",\n\t\t\ttenancyMgr:     NewManager(&Options{Enabled: true}),\n\t\t\trequestHeaders: map[string][]string{},\n\t\t\tshouldReach:    false,\n\t\t},\n\t\t{\n\t\t\tname:           \"valid tenant header\",\n\t\t\ttenancyMgr:     NewManager(&Options{Enabled: true}),\n\t\t\trequestHeaders: map[string][]string{\"x-tenant\": {\"acme\"}},\n\t\t\tshouldReach:    true,\n\t\t},\n\t\t{\n\t\t\tname:           \"unauthorized tenant\",\n\t\t\ttenancyMgr:     NewManager(&Options{Enabled: true, Tenants: []string{\"megacorp\"}}),\n\t\t\trequestHeaders: map[string][]string{\"x-tenant\": {\"acme\"}},\n\t\t\tshouldReach:    false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\thandler := &testHttpHandler{}\n\t\t\tpropH := ExtractTenantHTTPHandler(test.tenancyMgr, handler)\n\t\t\treq, err := http.NewRequest(http.MethodGet, \"/\", strings.NewReader(\"\"))\n\t\t\tfor k, vs := range test.requestHeaders {\n\t\t\t\tfor _, v := range vs {\n\t\t\t\t\treq.Header.Add(k, v)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\twriter := httptest.NewRecorder()\n\t\t\tpropH.ServeHTTP(writer, req)\n\t\t\tassert.Equal(t, test.shouldReach, handler.reached)\n\t\t})\n\t}\n}\n\nfunc TestMetadataAnnotator(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\ttenancyMgr     *Manager\n\t\trequestHeaders map[string][]string\n\t}{\n\t\t{\n\t\t\tname:           \"missing tenant\",\n\t\t\ttenancyMgr:     NewManager(&Options{Enabled: true}),\n\t\t\trequestHeaders: map[string][]string{},\n\t\t},\n\t\t{\n\t\t\tname:           \"tenanted\",\n\t\t\ttenancyMgr:     NewManager(&Options{Enabled: true}),\n\t\t\trequestHeaders: map[string][]string{\"x-tenant\": {\"acme\"}},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treq, err := http.NewRequest(http.MethodGet, \"/\", strings.NewReader(\"\"))\n\t\t\tfor k, vs := range test.requestHeaders {\n\t\t\t\tfor _, v := range vs {\n\t\t\t\t\treq.Header.Add(k, v)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tannotator := test.tenancyMgr.MetadataAnnotator()\n\t\t\tmd := annotator(context.Background(), req)\n\t\t\tassert.Len(t, md, len(test.requestHeaders))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/tenancy/manage_test.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTenancyValidity(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\toptions Options\n\t\ttenant  string\n\t\tvalid   bool\n\t}{\n\t\t{\n\t\t\tname: \"valid single tenant\",\n\t\t\toptions: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{\"acme\"},\n\t\t\t},\n\t\t\ttenant: \"acme\",\n\t\t\tvalid:  true,\n\t\t},\n\t\t{\n\t\t\tname: \"valid tenant in multi-tenant setup\",\n\t\t\toptions: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{\"acme\", \"country-store\"},\n\t\t\t},\n\t\t\ttenant: \"acme\",\n\t\t\tvalid:  true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid tenant\",\n\t\t\toptions: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{\"acme\", \"country-store\"},\n\t\t\t},\n\t\t\ttenant: \"auto-repair\",\n\t\t\tvalid:  false,\n\t\t},\n\t\t{\n\t\t\t// Not supplying a list of tenants will mean\n\t\t\t// \"tenant header required, but any value will pass\"\n\t\t\tname: \"any tenant\",\n\t\t\toptions: Options{\n\t\t\t\tEnabled: true,\n\t\t\t\tHeader:  \"x-tenant\",\n\t\t\t\tTenants: []string{},\n\t\t\t},\n\t\t\ttenant: \"convenience-store\",\n\t\t\tvalid:  true,\n\t\t},\n\t\t{\n\t\t\tname: \"ignore tenant\",\n\t\t\toptions: Options{\n\t\t\t\tEnabled: false,\n\t\t\t\tHeader:  \"\",\n\t\t\t\tTenants: []string{\"acme\"},\n\t\t\t},\n\t\t\ttenant: \"country-store\",\n\t\t\t// If tenancy not enabled, any tenant is valid\n\t\t\tvalid: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ttc := NewManager(&test.options)\n\t\t\tassert.Equal(t, test.valid, tc.Valid(test.tenant))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/tenancy/manager.go",
    "content": "// Copyright (c) 2022 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\n// Options describes the configuration properties for multitenancy\ntype Options struct {\n\tEnabled bool\n\tHeader  string\n\tTenants []string\n}\n\n// Manager can check tenant usage for multi-tenant Jaeger configurations\ntype Manager struct {\n\tEnabled bool\n\tHeader  string\n\tguard   guard\n}\n\n// Guard verifies a valid tenant when tenancy is enabled\ntype guard interface {\n\tValid(candidate string) bool\n}\n\n// NewManager creates a tenancy.Manager for given tenancy.Options.\nfunc NewManager(options *Options) *Manager {\n\t// Default header value (although set by CLI flags, this helps tests and API users)\n\theader := options.Header\n\tif header == \"\" && options.Enabled {\n\t\theader = \"x-tenant\"\n\t}\n\treturn &Manager{\n\t\tEnabled: options.Enabled,\n\t\tHeader:  header,\n\t\tguard:   tenancyGuardFactory(options),\n\t}\n}\n\nfunc (tc *Manager) Valid(tenant string) bool {\n\treturn tc.guard.Valid(tenant)\n}\n\ntype tenantDontCare bool\n\nfunc (tenantDontCare) Valid(string /* candidate */) bool {\n\treturn true\n}\n\ntype tenantList struct {\n\ttenants map[string]bool\n}\n\nfunc (tl *tenantList) Valid(candidate string) bool {\n\t_, ok := tl.tenants[candidate]\n\treturn ok\n}\n\nfunc newTenantList(tenants []string) *tenantList {\n\ttenantMap := make(map[string]bool)\n\tfor _, tenant := range tenants {\n\t\ttenantMap[tenant] = true\n\t}\n\n\treturn &tenantList{\n\t\ttenants: tenantMap,\n\t}\n}\n\nfunc tenancyGuardFactory(options *Options) guard {\n\t// Three cases\n\t// - no tenancy\n\t// - tenancy, but no guarding by tenant\n\t// - tenancy, with guarding by a list\n\n\tif !options.Enabled || len(options.Tenants) == 0 {\n\t\treturn tenantDontCare(true)\n\t}\n\n\treturn newTenantList(options.Tenants)\n}\n"
  },
  {
    "path": "internal/tenancy/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tenancy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/testutils/leakcheck.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage testutils\n\nimport (\n\t\"testing\"\n\n\t\"go.uber.org/goleak\"\n)\n\n// IgnoreGlogFlushDaemonLeak returns a goleak.Option that ignores the flushDaemon function\n// from the glog package that can cause false positives in leak detection.\n// This is necessary because glog starts a goroutine in the background that may not\n// be stopped when the test finishes, leading to a detected but expected leak.\nfunc IgnoreGlogFlushDaemonLeak() goleak.Option {\n\treturn goleak.IgnoreTopFunction(\"github.com/golang/glog.(*fileSink).flushDaemon\")\n}\n\n// IgnoreOpenCensusWorkerLeak This prevent catching the leak generated by opencensus defaultWorker.Start which at the time is part of the package's init call.\n// See https://github.com/jaegertracing/jaeger/pull/5055#discussion_r1438702168 for more context.\nfunc IgnoreOpenCensusWorkerLeak() goleak.Option {\n\treturn goleak.IgnoreTopFunction(\"go.opencensus.io/stats/view.(*worker).start\")\n}\n\n// IgnoreGoMetricsMeterLeak prevents the leak created by go-metrics which is\n// used by Sarama (Kafka Client) in Jaeger v1. This reason of this leak is\n// not Jaeger but the go-metrics used by Samara.\n// See these issues for the context\n// - https://github.com/IBM/sarama/issues/1321\n// - https://github.com/IBM/sarama/issues/1340\n// - https://github.com/IBM/sarama/issues/2832\nfunc IgnoreGoMetricsMeterLeak() goleak.Option {\n\treturn goleak.IgnoreTopFunction(\"github.com/rcrowley/go-metrics.(*meterArbiter).tick\")\n}\n\n// Don't use this in any other method other than leaks for ElasticSearch and OpenSearch\n// These leaks are from olivere client not from the jaeger\n// See this PR for context: https://github.com/jaegertracing/jaeger/pull/6339\nfunc ignoreHttpTransportWriteLoopLeak() goleak.Option {\n\treturn goleak.IgnoreTopFunction(\"net/http.(*persistConn).writeLoop\")\n}\n\n// Don't use this in any other method other than leaks for ElasticSearch and OpenSearch\n// These leaks are from olivere client not from the jaeger\n// See this PR for context: https://github.com/jaegertracing/jaeger/pull/6339\nfunc ignoreHttpTransportPollRuntimeLeak() goleak.Option {\n\treturn goleak.IgnoreTopFunction(\"internal/poll.runtime_pollWait\")\n}\n\n// Don't use this in any other method other than leaks for ElasticSearch and OpenSearch\n// These leaks are from olivere client not from the jaeger\n// See this PR for context: https://github.com/jaegertracing/jaeger/pull/6339\nfunc ignoreHttpTransportReadLoopLeak() goleak.Option {\n\treturn goleak.IgnoreTopFunction(\"net/http.(*persistConn).readLoop\")\n}\n\n// VerifyGoLeaks verifies that unit tests do not leak any goroutines.\n// It should be called in TestMain.\nfunc VerifyGoLeaks(m *testing.M) {\n\tgoleak.VerifyTestMain(m, IgnoreGlogFlushDaemonLeak(), IgnoreOpenCensusWorkerLeak(), IgnoreGoMetricsMeterLeak())\n}\n\n// VerifyGoLeaksOnce verifies that a given unit test does not leak any goroutines.\n// Occasionally useful to troubleshoot specific tests that are flaky due to leaks,\n// since VerifyGoLeaks cannot distiguish which specific test caused the leak.\n// It should be called via defer or from Cleanup:\n//\n//\tdefer testutils.VerifyGoLeaksOnce(t)\nfunc VerifyGoLeaksOnce(t *testing.T) {\n\tgoleak.VerifyNone(t, IgnoreGlogFlushDaemonLeak(), IgnoreOpenCensusWorkerLeak(), IgnoreGoMetricsMeterLeak())\n}\n\n// VerifyGoLeaksOnceForES is go leak check for ElasticSearch integration tests (v1)\n// This must not be used anywhere else other than integration package for v1\nfunc VerifyGoLeaksOnceForES(t *testing.T) {\n\tgoleak.VerifyNone(t, ignoreHttpTransportWriteLoopLeak(), ignoreHttpTransportPollRuntimeLeak(), ignoreHttpTransportReadLoopLeak())\n}\n\n// VerifyGoLeaksForES is go leak check for integration package in ElasticSearch Environment\n// This must not be used anywhere else other than integration package in ES environment for v1\nfunc VerifyGoLeaksForES(m *testing.M) {\n\tgoleak.VerifyTestMain(m, ignoreHttpTransportWriteLoopLeak(), ignoreHttpTransportPollRuntimeLeak(), ignoreHttpTransportReadLoopLeak())\n}\n"
  },
  {
    "path": "internal/testutils/leakcheck_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage testutils_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestVerifyGoLeaksOnce(t *testing.T) {\n\ttestutils.VerifyGoLeaksOnce(t)\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/testutils/logger.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage testutils\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"go.uber.org/zap/zaptest\"\n)\n\n// NewLogger creates a new zap.Logger backed by a zaptest.Buffer, which is also returned.\nfunc NewLogger() (*zap.Logger, *Buffer) {\n\tcore, buf := newRecordingCore()\n\tlogger := zap.New(core, zap.WithFatalHook(zapcore.WriteThenPanic))\n\treturn logger, buf\n}\n\nfunc newRecordingCore() (zapcore.Core, *Buffer) {\n\tencoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{\n\t\tMessageKey:     \"msg\",\n\t\tLevelKey:       \"level\",\n\t\tEncodeLevel:    zapcore.LowercaseLevelEncoder,\n\t\tEncodeTime:     zapcore.ISO8601TimeEncoder,\n\t\tEncodeDuration: zapcore.StringDurationEncoder,\n\t})\n\tbuf := &Buffer{}\n\treturn zapcore.NewCore(encoder, buf, zapcore.DebugLevel), buf\n}\n\n// NewEchoLogger is similar to NewLogger, but the logs are also echoed to t.Log.\nfunc NewEchoLogger(t *testing.T) (*zap.Logger, *Buffer) {\n\tcore, buf := newRecordingCore()\n\techo := zaptest.NewLogger(t).Core()\n\tlogger := zap.New(zapcore.NewTee(core, echo))\n\treturn logger, buf\n}\n\n// Buffer wraps zaptest.Buffer and provides convenience method JSONLine(n)\ntype Buffer struct {\n\tmu sync.RWMutex\n\tzaptest.Buffer\n}\n\n// JSONLine reads n-th line from the buffer and converts it to JSON.\nfunc (b *Buffer) JSONLine(n int) map[string]string {\n\tdata := make(map[string]string)\n\tline := b.Lines()[n]\n\tif err := json.Unmarshal([]byte(line), &data); err != nil {\n\t\treturn map[string]string{\n\t\t\t\"error\": err.Error(),\n\t\t}\n\t}\n\treturn data\n}\n\n// NB. the below functions overwrite the existing functions so that logger is threadsafe.\n// This is not that fragile given how if the API were to change underneath in zap, the overwritten\n// function will fail to compile.\n\n// Lines overwrites zaptest.Buffer.Lines() to make it thread safe\nfunc (b *Buffer) Lines() []string {\n\tb.mu.RLock()\n\tdefer b.mu.RUnlock()\n\treturn b.Buffer.Lines()\n}\n\n// Stripped overwrites zaptest.Buffer.Stripped() to make it thread safe\nfunc (b *Buffer) Stripped() string {\n\tb.mu.RLock()\n\tdefer b.mu.RUnlock()\n\treturn b.Buffer.Stripped()\n}\n\n// String overwrites zaptest.Buffer.String() to make it thread safe\nfunc (b *Buffer) String() string {\n\tb.mu.RLock()\n\tdefer b.mu.RUnlock()\n\treturn b.Buffer.String()\n}\n\n// Write overwrites zaptest.Buffer.bytes.Buffer.Write() to make it thread safe\nfunc (b *Buffer) Write(p []byte) (int, error) {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\treturn b.Buffer.Write(p)\n}\n\n// LogMatcher is a helper func that returns true if the subStr appears more than 'occurrences' times in the logs.\nvar LogMatcher = func(occurrences int, subStr string, logs []string) (bool, string) {\n\terrMsg := fmt.Sprintf(\"subStr '%s' does not occur %d time(s) in %v\", subStr, occurrences, logs)\n\tif len(logs) < occurrences {\n\t\treturn false, errMsg\n\t}\n\tvar count int\n\tfor _, log := range logs {\n\t\tif strings.Contains(log, subStr) {\n\t\t\tcount++\n\t\t}\n\t}\n\tif count >= occurrences {\n\t\treturn true, \"\"\n\t}\n\treturn false, errMsg\n}\n"
  },
  {
    "path": "internal/testutils/logger_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage testutils\n\nimport (\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestNewLogger(t *testing.T) {\n\tlogger, log := NewLogger()\n\tlogger.Warn(\"hello\", zap.String(\"x\", \"y\"))\n\n\tassert.JSONEq(t, `{\"level\":\"warn\",\"msg\":\"hello\",\"x\":\"y\"}`, log.Lines()[0])\n\tassert.Equal(t, map[string]string{\n\t\t\"level\": \"warn\",\n\t\t\"msg\":   \"hello\",\n\t\t\"x\":     \"y\",\n\t}, log.JSONLine(0))\n}\n\nfunc TestNewEchoLogger(t *testing.T) {\n\tlogger, _ := NewEchoLogger(t)\n\tlogger.Warn(\"hello\", zap.String(\"x\", \"y\"))\n}\n\nfunc TestJSONLineError(t *testing.T) {\n\tlog := &Buffer{}\n\tlog.WriteString(\"bad-json\\n\")\n\t_, ok := log.JSONLine(0)[\"error\"]\n\tassert.True(t, ok, \"must have 'error' key\")\n}\n\n// NB. Run with -race to ensure no race condition\nfunc TestRaceCondition(*testing.T) {\n\tlogger, buffer := NewLogger()\n\n\tstart := make(chan struct{})\n\tfinish := sync.WaitGroup{}\n\tfinish.Add(2)\n\n\tgo func() {\n\t\t<-start\n\t\tlogger.Info(\"test\")\n\t\tfinish.Done()\n\t}()\n\n\tgo func() {\n\t\t<-start\n\t\tbuffer.Lines()\n\t\tbuffer.Stripped()\n\t\t_ = buffer.String()\n\t\tfinish.Done()\n\t}()\n\n\tclose(start)\n\tfinish.Wait()\n}\n\nfunc TestLogMatcher(t *testing.T) {\n\ttests := []struct {\n\t\toccurrences int\n\t\tsubStr      string\n\t\tlogs        []string\n\t\texpected    bool\n\t\terrMsg      string\n\t}{\n\t\t{occurrences: 1, expected: false, errMsg: \"subStr '' does not occur 1 time(s) in []\"},\n\t\t{occurrences: 1, subStr: \"hi\", logs: []string{\"hi\"}, expected: true},\n\t\t{occurrences: 3, subStr: \"hi\", logs: []string{\"hi\", \"hi\"}, expected: false, errMsg: \"subStr 'hi' does not occur 3 time(s) in [hi hi]\"},\n\t\t{occurrences: 3, subStr: \"hi\", logs: []string{\"hi\", \"hi\", \"hi\"}, expected: true},\n\t\t{occurrences: 1, subStr: \"hi\", logs: []string{\"bye\", \"bye\"}, expected: false, errMsg: \"subStr 'hi' does not occur 1 time(s) in [bye bye]\"},\n\t}\n\tfor i, tt := range tests {\n\t\ttest := tt\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\tmatch, errMsg := LogMatcher(test.occurrences, test.subStr, test.logs)\n\t\t\tassert.Equal(t, test.expected, match)\n\t\t\tassert.Equal(t, test.errMsg, errMsg)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/tools/empty.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package tools is used to track versions of 3rd party tools used for building / testing  CI.\n// See tools.go for imported tools and go.mod for the versions of those tools.\npackage tools\n"
  },
  {
    "path": "internal/tools/go.mod",
    "content": "module github.com/jaegertracing/jaeger/internal/tools\n\ngo 1.26.0\n\nrequire (\n\tgithub.com/golangci/golangci-lint/v2 v2.10.1\n\tgithub.com/josephspurrier/goversioninfo v1.5.0\n\tgithub.com/open-telemetry/opentelemetry-collector-contrib/cmd/schemagen v0.147.0\n\tgithub.com/vektra/mockery/v3 v3.6.1\n\tgithub.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad\n\tgolang.org/x/vuln v1.1.4\n\tmvdan.cc/gofumpt v0.9.2\n)\n\nrequire (\n\t4d63.com/gocheckcompilerdirectives v1.3.0 // indirect\n\t4d63.com/gochecknoglobals v0.2.2 // indirect\n\tcodeberg.org/chavacava/garif v0.2.0 // indirect\n\tcodeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect\n\tdev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect\n\tdev.gaijin.team/go/golib v0.6.0 // indirect\n\tgithub.com/4meepo/tagalign v1.4.3 // indirect\n\tgithub.com/Abirdcfly/dupword v0.1.7 // indirect\n\tgithub.com/AdminBenni/iota-mixing v1.0.0 // indirect\n\tgithub.com/AlwxSin/noinlineerr v1.0.5 // indirect\n\tgithub.com/Antonboom/errname v1.1.1 // indirect\n\tgithub.com/Antonboom/nilnil v1.1.1 // indirect\n\tgithub.com/Antonboom/testifylint v1.6.4 // indirect\n\tgithub.com/BurntSushi/toml v1.6.0 // indirect\n\tgithub.com/Djarvur/go-err113 v0.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/MirrexOne/unqueryvet v1.5.3 // indirect\n\tgithub.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect\n\tgithub.com/akavel/rsrc v0.10.2 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.23.1 // indirect\n\tgithub.com/alecthomas/go-check-sumtype v0.3.1 // indirect\n\tgithub.com/alexkohler/nakedret/v2 v2.0.6 // indirect\n\tgithub.com/alexkohler/prealloc v1.0.2 // indirect\n\tgithub.com/alfatraining/structtag v1.0.0 // indirect\n\tgithub.com/alingse/asasalint v0.0.11 // indirect\n\tgithub.com/alingse/nilnesserr v0.2.0 // indirect\n\tgithub.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect\n\tgithub.com/ashanbrown/makezero/v2 v2.1.0 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bkielbasa/cyclop v1.2.3 // indirect\n\tgithub.com/blizzy78/varnamelen v0.8.0 // indirect\n\tgithub.com/bombsimon/wsl/v4 v4.7.0 // indirect\n\tgithub.com/bombsimon/wsl/v5 v5.6.0 // indirect\n\tgithub.com/breml/bidichk v0.3.3 // indirect\n\tgithub.com/breml/errchkjson v0.4.1 // indirect\n\tgithub.com/brunoga/deep v1.2.4 // indirect\n\tgithub.com/butuzov/ireturn v0.4.0 // indirect\n\tgithub.com/butuzov/mirror v1.3.0 // indirect\n\tgithub.com/catenacyber/perfsprint v0.10.1 // indirect\n\tgithub.com/ccojocar/zxcvbn-go v1.0.4 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/charithe/durationcheck v0.0.11 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect\n\tgithub.com/charmbracelet/lipgloss v1.1.0 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.10.1 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect\n\tgithub.com/charmbracelet/x/term v0.2.1 // indirect\n\tgithub.com/ckaznocha/intrange v0.3.1 // indirect\n\tgithub.com/curioswitch/go-reassign v0.3.0 // indirect\n\tgithub.com/daixiang0/gci v0.13.7 // indirect\n\tgithub.com/dave/dst v0.27.3 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/denis-tingaikin/go-header v0.5.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.5 // indirect\n\tgithub.com/ettle/strcase v0.2.0 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/fatih/structs v1.1.0 // indirect\n\tgithub.com/fatih/structtag v1.2.0 // indirect\n\tgithub.com/firefart/nonamedreturns v1.0.6 // indirect\n\tgithub.com/fsnotify/fsnotify v1.8.0 // indirect\n\tgithub.com/fzipp/gocyclo v0.6.0 // indirect\n\tgithub.com/ghostiam/protogetter v0.3.20 // indirect\n\tgithub.com/go-critic/go-critic v0.14.3 // indirect\n\tgithub.com/go-toolsmith/astcast v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astcopy v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astequal v1.2.0 // indirect\n\tgithub.com/go-toolsmith/astfmt v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astp v1.1.0 // indirect\n\tgithub.com/go-toolsmith/strparse v1.1.0 // indirect\n\tgithub.com/go-toolsmith/typep v1.1.0 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0 // indirect\n\tgithub.com/go-xmlfmt/xmlfmt v1.1.3 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/godoc-lint/godoc-lint v0.11.2 // indirect\n\tgithub.com/gofrs/flock v0.13.0 // indirect\n\tgithub.com/golang/protobuf v1.5.3 // indirect\n\tgithub.com/golangci/asciicheck v0.5.0 // indirect\n\tgithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect\n\tgithub.com/golangci/go-printf-func-name v0.1.1 // indirect\n\tgithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect\n\tgithub.com/golangci/golines v0.15.0 // indirect\n\tgithub.com/golangci/misspell v0.8.0 // indirect\n\tgithub.com/golangci/plugin-module-register v0.1.2 // indirect\n\tgithub.com/golangci/revgrep v0.8.0 // indirect\n\tgithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect\n\tgithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/gordonklaus/ineffassign v0.2.0 // indirect\n\tgithub.com/gostaticanalysis/analysisutil v0.7.1 // indirect\n\tgithub.com/gostaticanalysis/comment v1.5.0 // indirect\n\tgithub.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect\n\tgithub.com/gostaticanalysis/nilerr v0.1.2 // indirect\n\tgithub.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hexops/gotextdiff v1.0.3 // indirect\n\tgithub.com/huandu/xstrings v1.5.0 // indirect\n\tgithub.com/iancoleman/strcase v0.3.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jedib0t/go-pretty/v6 v6.6.7 // indirect\n\tgithub.com/jgautheron/goconst v1.8.2 // indirect\n\tgithub.com/jingyugao/rowserrcheck v1.1.1 // indirect\n\tgithub.com/jjti/go-spancheck v0.6.5 // indirect\n\tgithub.com/julz/importas v0.2.0 // indirect\n\tgithub.com/karamaru-alpha/copyloopvar v1.2.2 // indirect\n\tgithub.com/kisielk/errcheck v1.9.0 // indirect\n\tgithub.com/kkHAIKE/contextcheck v1.1.6 // indirect\n\tgithub.com/knadh/koanf/maps v0.1.2 // indirect\n\tgithub.com/knadh/koanf/parsers/yaml v0.1.0 // indirect\n\tgithub.com/knadh/koanf/providers/env v1.0.0 // indirect\n\tgithub.com/knadh/koanf/providers/file v1.1.2 // indirect\n\tgithub.com/knadh/koanf/providers/posflag v0.1.0 // indirect\n\tgithub.com/knadh/koanf/providers/structs v0.1.0 // indirect\n\tgithub.com/knadh/koanf/v2 v2.3.0 // indirect\n\tgithub.com/kulti/thelper v0.7.1 // indirect\n\tgithub.com/kunwardeep/paralleltest v1.0.15 // indirect\n\tgithub.com/lasiar/canonicalheader v1.1.2 // indirect\n\tgithub.com/ldez/exptostd v0.4.5 // indirect\n\tgithub.com/ldez/gomoddirectives v0.8.0 // indirect\n\tgithub.com/ldez/grignotin v0.10.1 // indirect\n\tgithub.com/ldez/structtags v0.6.1 // indirect\n\tgithub.com/ldez/tagliatelle v0.7.2 // indirect\n\tgithub.com/ldez/usetesting v0.5.0 // indirect\n\tgithub.com/leonklingele/grouper v1.1.2 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/macabu/inamedparam v0.2.0 // indirect\n\tgithub.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect\n\tgithub.com/manuelarte/funcorder v0.5.0 // indirect\n\tgithub.com/maratori/testableexamples v1.0.1 // indirect\n\tgithub.com/maratori/testpackage v1.1.2 // indirect\n\tgithub.com/matoous/godox v1.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect\n\tgithub.com/mgechev/revive v1.14.0 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/moricho/tparallel v0.3.2 // indirect\n\tgithub.com/muesli/termenv v0.16.0 // indirect\n\tgithub.com/nakabonne/nestif v0.3.1 // indirect\n\tgithub.com/nishanths/exhaustive v0.12.0 // indirect\n\tgithub.com/nishanths/predeclared v0.2.2 // indirect\n\tgithub.com/nunnatsa/ginkgolinter v0.23.0 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_golang v1.12.1 // indirect\n\tgithub.com/prometheus/client_model v0.2.0 // indirect\n\tgithub.com/prometheus/common v0.32.1 // indirect\n\tgithub.com/prometheus/procfs v0.7.3 // indirect\n\tgithub.com/quasilyte/go-ruleguard v0.4.5 // indirect\n\tgithub.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect\n\tgithub.com/quasilyte/gogrep v0.5.0 // indirect\n\tgithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect\n\tgithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect\n\tgithub.com/raeperd/recvcheck v0.2.0 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/rogpeppe/go-internal v1.14.1 // indirect\n\tgithub.com/rs/zerolog v1.33.0 // indirect\n\tgithub.com/ryancurrah/gomodguard v1.4.1 // indirect\n\tgithub.com/ryanrolds/sqlclosecheck v0.5.1 // indirect\n\tgithub.com/sagikazarmark/locafero v0.7.0 // indirect\n\tgithub.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect\n\tgithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect\n\tgithub.com/sashamelentyev/interfacebloat v1.1.0 // indirect\n\tgithub.com/sashamelentyev/usestdlibvars v1.29.0 // indirect\n\tgithub.com/securego/gosec/v2 v2.23.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.4 // indirect\n\tgithub.com/sivchari/containedctx v1.0.3 // indirect\n\tgithub.com/sonatard/noctx v0.4.0 // indirect\n\tgithub.com/sourcegraph/conc v0.3.0 // indirect\n\tgithub.com/sourcegraph/go-diff v0.7.0 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/spf13/cast v1.7.1 // indirect\n\tgithub.com/spf13/cobra v1.10.2 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/spf13/viper v1.20.0 // indirect\n\tgithub.com/ssgreg/nlreturn/v2 v2.2.1 // indirect\n\tgithub.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/stretchr/testify v1.11.1 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/tetafro/godot v1.5.4 // indirect\n\tgithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect\n\tgithub.com/timonwong/loggercheck v0.11.0 // indirect\n\tgithub.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect\n\tgithub.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect\n\tgithub.com/ultraware/funlen v0.2.0 // indirect\n\tgithub.com/ultraware/whitespace v0.2.0 // indirect\n\tgithub.com/uudashr/gocognit v1.2.0 // indirect\n\tgithub.com/uudashr/iface v1.4.1 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgithub.com/xen0n/gosmopolitan v1.3.0 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/yagipy/maintidx v1.0.0 // indirect\n\tgithub.com/yeya24/promlinter v0.3.0 // indirect\n\tgithub.com/ykadowak/zerologlint v0.1.5 // indirect\n\tgitlab.com/bosi/decorder v0.4.2 // indirect\n\tgo-simpler.org/musttag v0.14.0 // indirect\n\tgo-simpler.org/sloglint v0.11.1 // indirect\n\tgo.augendre.info/arangolint v0.4.0 // indirect\n\tgo.augendre.info/fatcontext v0.9.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect\n\tgolang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect\n\tgolang.org/x/term v0.39.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\thonnef.co/go/tools v0.7.0 // indirect\n\tmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect\n)\n"
  },
  {
    "path": "internal/tools/go.sum",
    "content": "4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=\n4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=\n4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=\n4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncodeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=\ncodeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=\ncodeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI=\ncodeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8=\ndev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=\ndev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=\ndev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=\ndev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=\ngithub.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=\ngithub.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ=\ngithub.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4=\ngithub.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=\ngithub.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=\ngithub.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=\ngithub.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=\ngithub.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q=\ngithub.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ=\ngithub.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ=\ngithub.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=\ngithub.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=\ngithub.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=\ngithub.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/MirrexOne/unqueryvet v1.5.3 h1:LpT3rsH+IY3cQddWF9bg4C7jsbASdGnrOSofY8IPEiw=\ngithub.com/MirrexOne/unqueryvet v1.5.3/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU=\ngithub.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=\ngithub.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=\ngithub.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=\ngithub.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=\ngithub.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=\ngithub.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=\ngithub.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=\ngithub.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=\ngithub.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=\ngithub.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=\ngithub.com/alexkohler/prealloc v1.0.2 h1:MPo8cIkGkZytq7WNH9UHv3DIX1mPz1RatPXnZb0zHWQ=\ngithub.com/alexkohler/prealloc v1.0.2/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig=\ngithub.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=\ngithub.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=\ngithub.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=\ngithub.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=\ngithub.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=\ngithub.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=\ngithub.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo=\ngithub.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c=\ngithub.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE=\ngithub.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=\ngithub.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=\ngithub.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=\ngithub.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=\ngithub.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=\ngithub.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=\ngithub.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8=\ngithub.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU=\ngithub.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=\ngithub.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=\ngithub.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=\ngithub.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=\ngithub.com/brunoga/deep v1.2.4 h1:Aj9E9oUbE+ccbyh35VC/NHlzzjfIVU69BXu2mt2LmL8=\ngithub.com/brunoga/deep v1.2.4/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2dgaI=\ngithub.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=\ngithub.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=\ngithub.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=\ngithub.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=\ngithub.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=\ngithub.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=\ngithub.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=\ngithub.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=\ngithub.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=\ngithub.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=\ngithub.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=\ngithub.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=\ngithub.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=\ngithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=\ngithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=\ngithub.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=\ngithub.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=\ngithub.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=\ngithub.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=\ngithub.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=\ngithub.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=\ngithub.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=\ngithub.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=\ngithub.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=\ngithub.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=\ngithub.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=\ngithub.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=\ngithub.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=\ngithub.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=\ngithub.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=\ngithub.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=\ngithub.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=\ngithub.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=\ngithub.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=\ngithub.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=\ngithub.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=\ngithub.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=\ngithub.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=\ngithub.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=\ngithub.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=\ngithub.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=\ngithub.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=\ngithub.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=\ngithub.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=\ngithub.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=\ngithub.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=\ngithub.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=\ngithub.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=\ngithub.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=\ngithub.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=\ngithub.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=\ngithub.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=\ngithub.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=\ngithub.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=\ngithub.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=\ngithub.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=\ngithub.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=\ngithub.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=\ngithub.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=\ngithub.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM=\ngithub.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo=\ngithub.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=\ngithub.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=\ngithub.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=\ngithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=\ngithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=\ngithub.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=\ngithub.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=\ngithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=\ngithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=\ngithub.com/golangci/golangci-lint/v2 v2.10.1 h1:flhw5Px6ojbLyEFzXvJn5B2HEdkkRlkhE1SnmCbQBiE=\ngithub.com/golangci/golangci-lint/v2 v2.10.1/go.mod h1:dBsrOk6zj0vDhlTv+IiJGqkDokR24IVTS7W3EVfPTQY=\ngithub.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=\ngithub.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=\ngithub.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg=\ngithub.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=\ngithub.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=\ngithub.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=\ngithub.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=\ngithub.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=\ngithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=\ngithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=\ngithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=\ngithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=\ngithub.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=\ngithub.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=\ngithub.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=\ngithub.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=\ngithub.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=\ngithub.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=\ngithub.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=\ngithub.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=\ngithub.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=\ngithub.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=\ngithub.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU=\ngithub.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA=\ngithub.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=\ngithub.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=\ngithub.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=\ngithub.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=\ngithub.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=\ngithub.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=\ngithub.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=\ngithub.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=\ngithub.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=\ngithub.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=\ngithub.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=\ngithub.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=\ngithub.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=\ngithub.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=\ngithub.com/josephspurrier/goversioninfo v1.5.0 h1:9TJtORoyf4YMoWSOo/cXFN9A/lB3PniJ91OxIH6e7Zg=\ngithub.com/josephspurrier/goversioninfo v1.5.0/go.mod h1:6MoTvFZ6GKJkzcdLnU5T/RGYUbHQbKpYeNP0AgQLd2o=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=\ngithub.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=\ngithub.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0=\ngithub.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=\ngithub.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=\ngithub.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=\ngithub.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=\ngithub.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=\ngithub.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=\ngithub.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=\ngithub.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=\ngithub.com/knadh/koanf/providers/env v1.0.0 h1:ufePaI9BnWH+ajuxGGiJ8pdTG0uLEUWC7/HDDPGLah0=\ngithub.com/knadh/koanf/providers/env v1.0.0/go.mod h1:mzFyRZueYhb37oPmC1HAv/oGEEuyvJDA98r3XAa8Gak=\ngithub.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=\ngithub.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=\ngithub.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U=\ngithub.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=\ngithub.com/knadh/koanf/providers/structs v0.1.0 h1:wJRteCNn1qvLtE5h8KQBvLJovidSdntfdyIbbCzEyE0=\ngithub.com/knadh/koanf/providers/structs v0.1.0/go.mod h1:sw2YZ3txUcqA3Z27gPlmmBzWn1h8Nt9O6EP/91MkcWE=\ngithub.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=\ngithub.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=\ngithub.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=\ngithub.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w=\ngithub.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=\ngithub.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=\ngithub.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=\ngithub.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=\ngithub.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=\ngithub.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk=\ngithub.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q=\ngithub.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=\ngithub.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=\ngithub.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk=\ngithub.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY=\ngithub.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk=\ngithub.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI=\ngithub.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=\ngithub.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=\ngithub.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=\ngithub.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=\ngithub.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=\ngithub.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=\ngithub.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=\ngithub.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=\ngithub.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=\ngithub.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=\ngithub.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=\ngithub.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=\ngithub.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc=\ngithub.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=\ngithub.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=\ngithub.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=\ngithub.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mgechev/revive v1.14.0 h1:CC2Ulb3kV7JFYt+izwORoS3VT/+Plb8BvslI/l1yZsc=\ngithub.com/mgechev/revive v1.14.0/go.mod h1:MvnujelCZBZCaoDv5B3foPo6WWgULSSFxvfxp7GsPfo=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=\ngithub.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=\ngithub.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=\ngithub.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=\ngithub.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=\ngithub.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=\ngithub.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=\ngithub.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8=\ngithub.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4=\ngithub.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=\ngithub.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=\ngithub.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=\ngithub.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/cmd/schemagen v0.147.0 h1:0+UQLw0n243+NSXefYMA+8FDlzRh+drZ14zuGB+fdUQ=\ngithub.com/open-telemetry/opentelemetry-collector-contrib/cmd/schemagen v0.147.0/go.mod h1:OINT7fivIXPsH4oMYFjnkreLeHimo7uayD3NZn+SwY8=\ngithub.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=\ngithub.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=\ngithub.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=\ngithub.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=\ngithub.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=\ngithub.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=\ngithub.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=\ngithub.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA=\ngithub.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=\ngithub.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY=\ngithub.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=\ngithub.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=\ngithub.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=\ngithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=\ngithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=\ngithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=\ngithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=\ngithub.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=\ngithub.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=\ngithub.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=\ngithub.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=\ngithub.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=\ngithub.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=\ngithub.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=\ngithub.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=\ngithub.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=\ngithub.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=\ngithub.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=\ngithub.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=\ngithub.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=\ngithub.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=\ngithub.com/securego/gosec/v2 v2.23.0 h1:h4TtF64qFzvnkqvsHC/knT7YC5fqyOCItlVR8+ptEBo=\ngithub.com/securego/gosec/v2 v2.23.0/go.mod h1:qRHEgXLFuYUDkI2T7W7NJAmOkxVhkR0x9xyHOIcMNZ0=\ngithub.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=\ngithub.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=\ngithub.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=\ngithub.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=\ngithub.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=\ngithub.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=\ngithub.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=\ngithub.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=\ngithub.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=\ngithub.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=\ngithub.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=\ngithub.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=\ngithub.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=\ngithub.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=\ngithub.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=\ngithub.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=\ngithub.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=\ngithub.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=\ngithub.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg=\ngithub.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=\ngithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=\ngithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=\ngithub.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=\ngithub.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=\ngithub.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is=\ngithub.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo=\ngithub.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=\ngithub.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=\ngithub.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=\ngithub.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=\ngithub.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=\ngithub.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=\ngithub.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=\ngithub.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=\ngithub.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=\ngithub.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=\ngithub.com/vektra/mockery/v3 v3.6.1 h1:YyqAXihdNML8y6SJnvPKYr+2HAHvBjdvqFu/fMYlX8g=\ngithub.com/vektra/mockery/v3 v3.6.1/go.mod h1:Oti3Df0WP8wwT31yuVri3QNsDeMUQU5Q4QEg8EabaBw=\ngithub.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0=\ngithub.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=\ngithub.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=\ngithub.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=\ngithub.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=\ngithub.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=\ngithub.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=\ngithub.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=\ngitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=\ngo-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=\ngo-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=\ngo-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=\ngo-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=\ngo-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=\ngo-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=\ngo.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50=\ngo.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA=\ngo.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE=\ngo.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=\ngolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=\ngolang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=\ngolang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=\ngolang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk=\ngolang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0=\ngolang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=\ngolang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=\ngolang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\ngolang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=\ngolang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=\nhonnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=\nmvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4=\nmvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s=\nmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI=\nmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "internal/tools/tools.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n//go:build tools\n\npackage tools\n\n// This file follows the recommendation at\n// https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module\n// on how to pin tooling dependencies to a go.mod file.\n// This ensures that all systems use the same version of tools in addition to regular dependencies.\n\nimport (\n\t_ \"mvdan.cc/gofumpt\"\n\n\t_ \"github.com/golangci/golangci-lint/v2/cmd/golangci-lint\"\n\t_ \"github.com/josephspurrier/goversioninfo/cmd/goversioninfo\"\n\t_ \"github.com/open-telemetry/opentelemetry-collector-contrib/cmd/schemagen\"\n\t_ \"github.com/vektra/mockery/v3\"\n\t_ \"github.com/wadey/gocovmerge\"\n\t_ \"golang.org/x/vuln/cmd/govulncheck\"\n)\n"
  },
  {
    "path": "internal/tracegen/config.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracegen\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n)\n\n// Config describes the test scenario.\ntype Config struct {\n\tWorkers       int\n\tServices      int\n\tTraces        int\n\tChildSpans    int\n\tAttributes    int\n\tAttrKeys      int\n\tAttrValues    int\n\tMarshal       bool\n\tDebug         bool\n\tFirehose      bool\n\tPause         time.Duration\n\tDuration      time.Duration\n\tService       string\n\tTraceExporter string\n}\n\n// Flags registers config flags.\nfunc (c *Config) Flags(fs *flag.FlagSet) {\n\tfs.IntVar(&c.Workers, \"workers\", 1, \"Number of workers (goroutines) to run\")\n\tfs.IntVar(&c.Traces, \"traces\", 1, \"Number of traces to generate in each worker (ignored if duration is provided)\")\n\tfs.IntVar(&c.ChildSpans, \"spans\", 1, \"Number of child spans to generate for each trace\")\n\tfs.IntVar(&c.Attributes, \"attrs\", 11, \"Number of attributes to generate for each child span\")\n\tfs.IntVar(&c.AttrKeys, \"attr-keys\", 97, \"Number of distinct attributes keys to use\")\n\tfs.IntVar(&c.AttrValues, \"attr-values\", 1000, \"Number of distinct values to allow for each attribute\")\n\tfs.BoolVar(&c.Debug, \"debug\", false, \"Whether to set DEBUG flag on the spans to force sampling\")\n\tfs.BoolVar(&c.Firehose, \"firehose\", false, \"Whether to set FIREHOSE flag on the spans to skip indexing\")\n\tfs.DurationVar(&c.Pause, \"pause\", time.Microsecond, \"How long to sleep before finishing each span. If set to 0s then a fake 123µs duration is used.\")\n\tfs.DurationVar(&c.Duration, \"duration\", 0, \"For how long to run the test if greater than 0s (overrides -traces).\")\n\tfs.StringVar(&c.Service, \"service\", \"tracegen\", \"Service name prefix to use\")\n\tfs.IntVar(&c.Services, \"services\", 1, \"Number of unique suffixes to add to service name when generating traces, e.g. tracegen-01 (but only one service per trace)\")\n\tfs.StringVar(&c.TraceExporter, \"trace-exporter\", \"otlp-http\", \"Trace exporter (otlp/otlp-http|otlp-grpc|stdout). Exporters can be additionally configured via environment variables, see https://github.com/jaegertracing/jaeger/blob/main/cmd/tracegen/README.md\")\n}\n\n// Run executes the test scenario.\nfunc Run(c *Config, tracers []trace.Tracer, logger *zap.Logger) error {\n\tif c.Duration > 0 {\n\t\tc.Traces = 0\n\t} else if c.Traces <= 0 {\n\t\treturn errors.New(\"either `traces` or `duration` must be greater than 0\")\n\t}\n\n\twg := sync.WaitGroup{}\n\tvar running uint32 = 1\n\tfor i := 0; i < c.Workers; i++ {\n\t\twg.Add(1)\n\t\tw := worker{\n\t\t\tid:      i,\n\t\t\ttracers: tracers,\n\t\t\tConfig:  *c,\n\t\t\trunning: &running,\n\t\t\twg:      &wg,\n\t\t\tlogger:  logger.With(zap.Int(\"worker\", i)),\n\t\t}\n\n\t\tgo w.simulateTraces()\n\t}\n\tif c.Duration > 0 {\n\t\ttime.Sleep(c.Duration)\n\t\tatomic.StoreUint32(&running, 0)\n\t}\n\twg.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "internal/tracegen/config_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracegen\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n)\n\nfunc Test_Run(t *testing.T) {\n\tlogger := zap.NewNop()\n\ttp := sdktrace.NewTracerProvider()\n\n\ttests := []struct {\n\t\tname        string\n\t\tconfig      *Config\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname:        \"Empty config\",\n\t\t\tconfig:      &Config{},\n\t\t\texpectedErr: errors.New(\"either `traces` or `duration` must be greater than 0\"),\n\t\t},\n\t\t{\n\t\t\tname: \"Non-empty config\",\n\t\t\tconfig: &Config{\n\t\t\t\tWorkers:    2,\n\t\t\t\tTraces:     10,\n\t\t\t\tChildSpans: 5,\n\t\t\t\tAttributes: 20,\n\t\t\t\tAttrKeys:   50,\n\t\t\t\tAttrValues: 100,\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Negative traces, positive duration\",\n\t\t\tconfig: &Config{\n\t\t\t\tTraces:   -7,\n\t\t\t\tDuration: 7,\n\t\t\t},\n\t\t\texpectedErr: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttracers := []trace.Tracer{tp.Tracer(\"Test-Tracer\")}\n\t\t\terr := Run(tt.config, tracers, logger)\n\t\t\tassert.Equal(t, tt.expectedErr, err)\n\t\t})\n\t}\n}\n\nfunc Test_Flags(t *testing.T) {\n\tfs := &flag.FlagSet{}\n\tconfig := &Config{}\n\texpectedConfig := &Config{\n\t\tWorkers:       1,\n\t\tTraces:        1,\n\t\tChildSpans:    1,\n\t\tAttributes:    11,\n\t\tAttrKeys:      97,\n\t\tAttrValues:    1000,\n\t\tDebug:         false,\n\t\tFirehose:      false,\n\t\tPause:         1000,\n\t\tDuration:      0,\n\t\tService:       \"tracegen\",\n\t\tServices:      1,\n\t\tTraceExporter: \"otlp-http\",\n\t}\n\n\tconfig.Flags(fs)\n\tassert.Equal(t, expectedConfig, config)\n}\n"
  },
  {
    "path": "internal/tracegen/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracegen\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/tracegen/worker.go",
    "content": "// Copyright (c) 2018 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracegen\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/trace\"\n\t\"go.uber.org/zap\"\n)\n\ntype worker struct {\n\ttracers []trace.Tracer\n\trunning *uint32 // pointer to shared flag that indicates it's time to stop the test\n\tid      int     // worker id\n\tConfig\n\twg     *sync.WaitGroup // notify when done\n\tlogger *zap.Logger\n\n\t// internal counters\n\ttraceNo   int\n\tattrKeyNo int\n\tattrValNo int\n}\n\nconst (\n\tfakeSpanDuration = 123 * time.Microsecond\n)\n\nfunc (w *worker) simulateTraces() {\n\tfor atomic.LoadUint32(w.running) == 1 {\n\t\tsvcNo := w.traceNo % len(w.tracers)\n\t\tw.simulateOneTrace(w.tracers[svcNo])\n\t\tw.traceNo++\n\t\tif w.Traces != 0 {\n\t\t\tif w.traceNo >= w.Traces {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tw.logger.Info(fmt.Sprintf(\"Worker %d generated %d traces\", w.id, w.traceNo))\n\tw.wg.Done()\n}\n\nfunc (w *worker) simulateOneTrace(tracer trace.Tracer) {\n\tctx := context.Background()\n\tattrs := []attribute.KeyValue{\n\t\tattribute.String(\"peer.service\", \"tracegen-server\"),\n\t\tattribute.String(\"peer.host.ipv4\", \"1.1.1.1\"),\n\t}\n\tif w.Debug {\n\t\tattrs = append(attrs, attribute.Bool(\"jaeger.debug\", true))\n\t}\n\tif w.Firehose {\n\t\tattrs = append(attrs, attribute.Bool(\"jaeger.firehose\", true))\n\t}\n\tstart := time.Now()\n\tctx, parent := tracer.Start(\n\t\tctx,\n\t\t\"lets-go\",\n\t\ttrace.WithSpanKind(trace.SpanKindServer),\n\t\ttrace.WithAttributes(attrs...),\n\t\ttrace.WithTimestamp(start),\n\t)\n\tw.simulateChildSpans(ctx, start, tracer)\n\n\tif w.Pause != 0 {\n\t\tparent.End()\n\t} else {\n\t\ttotalDuration := time.Duration(w.ChildSpans) * fakeSpanDuration\n\t\tparent.End(\n\t\t\ttrace.WithTimestamp(start.Add(totalDuration)),\n\t\t)\n\t}\n}\n\nfunc (w *worker) simulateChildSpans(ctx context.Context, start time.Time, tracer trace.Tracer) {\n\tfor c := 0; c < w.ChildSpans; c++ {\n\t\tvar attrs []attribute.KeyValue\n\t\tfor a := 0; a < w.Attributes; a++ {\n\t\t\tkey := fmt.Sprintf(\"attr_%02d\", w.attrKeyNo)\n\t\t\tval := fmt.Sprintf(\"val_%02d\", w.attrValNo)\n\t\t\tattrs = append(attrs, attribute.String(key, val))\n\t\t\tw.attrKeyNo = (w.attrKeyNo + 1) % w.AttrKeys\n\t\t\tw.attrValNo = (w.attrValNo + 1) % w.AttrValues\n\t\t}\n\t\topts := []trace.SpanStartOption{\n\t\t\ttrace.WithSpanKind(trace.SpanKindClient),\n\t\t\ttrace.WithAttributes(attrs...),\n\t\t}\n\t\tchildStart := start.Add(time.Duration(c) * fakeSpanDuration)\n\t\tif w.Pause == 0 {\n\t\t\topts = append(opts, trace.WithTimestamp(childStart))\n\t\t}\n\t\t_, child := tracer.Start(\n\t\t\tctx,\n\t\t\tfmt.Sprintf(\"child-span-%02d\", c),\n\t\t\topts...,\n\t\t)\n\t\tif w.Pause != 0 {\n\t\t\ttime.Sleep(w.Pause)\n\t\t\tchild.End()\n\t\t} else {\n\t\t\tchild.End(\n\t\t\t\ttrace.WithTimestamp(childStart.Add(fakeSpanDuration)),\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/tracegen/worker_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage tracegen\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc Test_SimulateTraces(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tpause time.Duration\n\t}{\n\t\t{\n\t\t\tname:  \"no pause\",\n\t\t\tpause: 0,\n\t\t},\n\t\t{\n\t\t\tname:  \"with pause\",\n\t\t\tpause: time.Second,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tlogger, buf := testutils.NewLogger()\n\t\t\ttp := sdktrace.NewTracerProvider()\n\t\t\ttracers := []trace.Tracer{tp.Tracer(\"stdout\")}\n\t\t\twg := sync.WaitGroup{}\n\t\t\twg.Add(1)\n\t\t\tvar running uint32 = 1\n\t\t\tworker := &worker{\n\t\t\t\tlogger:  logger,\n\t\t\t\ttracers: tracers,\n\t\t\t\twg:      &wg,\n\t\t\t\tid:      7,\n\t\t\t\trunning: &running,\n\t\t\t\tConfig: Config{\n\t\t\t\t\tTraces:     7,\n\t\t\t\t\tDuration:   time.Second,\n\t\t\t\t\tPause:      tt.pause,\n\t\t\t\t\tService:    \"stdout\",\n\t\t\t\t\tDebug:      true,\n\t\t\t\t\tFirehose:   true,\n\t\t\t\t\tChildSpans: 1,\n\t\t\t\t},\n\t\t\t}\n\t\t\texpectedOutput := `{\"level\":\"info\",\"msg\":\"Worker 7 generated 7 traces\"}` + \"\\n\"\n\t\t\tworker.simulateTraces()\n\t\t\tassert.Equal(t, expectedOutput, buf.String())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package json allows converting model.Trace to external JSON data model.\npackage json\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/fixtures/domain_01.json",
    "content": "{\n  \"spans\": [\n    {\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAI=\",\n      \"operationName\": \"test-general-conversion\",\n      \"startTime\": \"2017-01-26T16:46:31.639875139-05:00\",\n      \"duration\": \"5000ns\",\n      \"process\": {\n        \"serviceName\": \"service-x\"\n      },\n      \"logs\": [\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875139-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"event\",\n              \"vStr\": \"some-event\"\n            }\n          ]\n        },\n        {\n          \"timestamp\": \"2017-01-26T16:46:31.639875139-05:00\",\n          \"fields\": [\n            {\n              \"key\": \"x\",\n              \"vStr\": \"y\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAI=\",\n      \"operationName\": \"some-operation\",\n      \"startTime\": \"2017-01-26T16:46:31.639875139-05:00\",\n      \"duration\": \"5000ns\",\n      \"tags\": [\n        {\n          \"key\": \"peer.service\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"service-y\"\n        },\n        {\n          \"key\": \"peer.ipv4\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 23456\n        },\n        {\n          \"key\": \"error\",\n          \"vType\": \"BOOL\",\n          \"vBool\": true\n        },\n        {\n          \"key\": \"temperature\",\n          \"vType\": \"FLOAT64\",\n          \"vFloat64\": 72.5\n        },\n        {\n          \"key\": \"javascript_limit\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 9223372036854775222\n        },\n        {\n          \"key\": \"blob\",\n          \"vType\": \"BINARY\",\n          \"vBinary\": \"AAAwOQ==\"\n        }\n      ],\n      \"process\": {\n        \"serviceName\": \"service-x\"\n      }\n    },\n    {\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAM=\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n          \"spanId\": \"AAAAAAAAAAI=\"\n        }\n      ],\n      \"operationName\": \"some-operation\",\n      \"startTime\": \"2017-01-26T16:46:31.639875139-05:00\",\n      \"duration\": \"5000ns\",\n      \"process\": {\n        \"serviceName\": \"service-y\"\n      }\n    },\n    {\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAQ=\",\n      \"operationName\": \"reference-test\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAAAAAAAAAAAA/w==\",\n          \"spanId\": \"AAAAAAAAAP8=\"\n        },\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n          \"spanId\": \"AAAAAAAAAAI=\"\n        },\n        {\n          \"refType\": \"FOLLOWS_FROM\",\n          \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n          \"spanId\": \"AAAAAAAAAAI=\"\n        }\n      ],\n      \"startTime\": \"2017-01-26T16:46:31.639875139-05:00\",\n      \"duration\": \"5000ns\",\n      \"process\": {\n        \"serviceName\": \"service-y\"\n      },\n      \"warnings\": [\n        \"some span warning\"\n      ]\n    },\n    {\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAU=\",\n      \"operationName\": \"preserveParentID-test\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n          \"spanId\": \"AAAAAAAAAAQ=\"\n        }\n      ],\n      \"startTime\": \"2017-01-26T16:46:31.639875139-05:00\",\n      \"duration\": \"4000ns\",\n      \"process\": {\n        \"serviceName\": \"service-y\"\n      },\n      \"warnings\": [\n        \"some span warning\"\n      ]\n    }\n  ],\n  \"processMap\": [],\n  \"warnings\": [\n    \"some trace warning\"\n  ]\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/fixtures/domain_es_01.json",
    "content": "{\n  \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n  \"spanId\": \"AAAAAAAAAAI=\",\n  \"operationName\": \"test-general-conversion\",\n  \"references\": [\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAM=\"\n    },\n    {\n      \"refType\": \"FOLLOWS_FROM\",\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAAAQ==\",\n      \"spanId\": \"AAAAAAAAAAQ=\"\n    },\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceId\": \"AAAAAAAAAAAAAAAAAAAA/w==\",\n      \"spanId\": \"AAAAAAAAAP8=\"\n    }\n  ],\n  \"flags\": 1,\n  \"startTime\": \"2017-01-26T16:46:31.639875-05:00\",\n  \"duration\": \"5000ns\",\n  \"tags\": [\n    {\n      \"key\": \"peer.service\",\n      \"vType\": \"STRING\",\n      \"vStr\": \"service-y\"\n    },\n    {\n      \"key\": \"peer.ipv4\",\n      \"vType\": \"INT64\",\n      \"vInt64\": 23456\n    },\n    {\n      \"key\": \"error\",\n      \"vType\": \"BOOL\",\n      \"vBool\": true\n    },\n    {\n      \"key\": \"temperature\",\n      \"vType\": \"FLOAT64\",\n      \"vFloat64\": 72.5\n    },\n    {\n      \"key\": \"blob\",\n      \"vType\": \"BINARY\",\n      \"vBinary\": \"AAAwOQ==\"\n    }\n  ],\n  \"logs\": [\n    {\n      \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"fields\": [\n        {\n          \"key\": \"event\",\n          \"vType\": \"INT64\",\n          \"vInt64\": 123415\n        }\n      ]\n    },\n    {\n      \"timestamp\": \"2017-01-26T16:46:31.639875-05:00\",\n      \"fields\": [\n        {\n          \"key\": \"x\",\n          \"vType\": \"STRING\",\n          \"vStr\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"process\": {\n    \"serviceName\": \"service-x\",\n    \"tags\": [\n      {\n        \"key\": \"peer.ipv4\",\n        \"vType\": \"INT64\",\n        \"vInt64\": 23456\n      },\n      {\n        \"key\": \"error\",\n        \"vType\": \"BOOL\",\n        \"vBool\": true\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/fixtures/es_01.json",
    "content": "{\n  \"traceID\": \"0000000000000001\",\n  \"spanID\": \"0000000000000002\",\n  \"flags\": 1,\n  \"operationName\": \"test-general-conversion\",\n  \"references\": [\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000003\"\n    },\n    {\n      \"refType\": \"FOLLOWS_FROM\",\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000004\"\n    },\n    {\n      \"refType\": \"CHILD_OF\",\n      \"traceID\": \"00000000000000ff\",\n      \"spanID\": \"00000000000000ff\"\n    }\n  ],\n  \"startTime\": 1485467191639875,\n  \"duration\": 5,\n  \"tags\": [\n    {\n      \"key\": \"peer.service\",\n      \"type\": \"string\",\n      \"value\": \"service-y\"\n    },\n    {\n      \"key\": \"peer.ipv4\",\n      \"type\": \"int64\",\n      \"value\": \"23456\"\n    },\n    {\n      \"key\": \"error\",\n      \"type\": \"bool\",\n      \"value\": \"true\"\n    },\n    {\n      \"key\": \"temperature\",\n      \"type\": \"float64\",\n      \"value\": \"72.5\"\n    },\n    {\n      \"key\": \"blob\",\n      \"type\": \"binary\",\n      \"value\": \"00003039\"\n    }\n  ],\n  \"logs\": [\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"event\",\n          \"type\": \"int64\",\n          \"value\": \"123415\"\n        }\n      ]\n    },\n    {\n      \"timestamp\": 1485467191639875,\n      \"fields\": [\n        {\n          \"key\": \"x\",\n          \"type\": \"string\",\n          \"value\": \"y\"\n        }\n      ]\n    }\n  ],\n  \"process\": {\n    \"serviceName\": \"service-x\",\n    \"tags\": [\n      {\n        \"key\": \"peer.ipv4\",\n        \"type\": \"int64\",\n        \"value\": \"23456\"\n      },\n      {\n        \"key\": \"error\",\n        \"type\": \"bool\",\n        \"value\": \"true\"\n      }\n    ]\n  },\n  \"warnings\": null\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/fixtures/ui_01.json",
    "content": "{\n  \"traceID\": \"0000000000000001\",\n  \"spans\": [\n    {\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000002\",\n      \"operationName\": \"test-general-conversion\",\n      \"references\": [],\n      \"startTime\": 1485467191639875,\n      \"duration\": 5,\n      \"tags\": [],\n      \"logs\": [\n        {\n          \"timestamp\": 1485467191639875,\n          \"fields\": [\n            {\n              \"key\": \"event\",\n              \"type\": \"string\",\n              \"value\": \"some-event\"\n            }\n          ]\n        },\n        {\n          \"timestamp\": 1485467191639875,\n          \"fields\": [\n            {\n              \"key\": \"x\",\n              \"type\": \"string\",\n              \"value\": \"y\"\n            }\n          ]\n        }\n      ],\n      \"processID\": \"p1\",\n      \"warnings\": null\n    },\n    {\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000002\",\n      \"operationName\": \"some-operation\",\n      \"references\": [],\n      \"startTime\": 1485467191639875,\n      \"duration\": 5,\n      \"tags\": [\n        {\n          \"key\": \"peer.service\",\n          \"type\": \"string\",\n          \"value\": \"service-y\"\n        },\n        {\n          \"key\": \"peer.ipv4\",\n          \"type\": \"int64\",\n          \"value\": 23456\n        },\n        {\n          \"key\": \"error\",\n          \"type\": \"bool\",\n          \"value\": true\n        },\n        {\n          \"key\": \"temperature\",\n          \"type\": \"float64\",\n          \"value\": 72.5\n        },\n        {\n          \"key\": \"javascript_limit\",\n          \"type\": \"int64\",\n          \"value\": \"9223372036854775222\"\n        },\n        {\n          \"key\": \"blob\",\n          \"type\": \"binary\",\n          \"value\": \"AAAwOQ==\"\n        }\n      ],\n      \"logs\": [],\n      \"processID\": \"p1\",\n      \"warnings\": null\n    },\n    {\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000003\",\n      \"operationName\": \"some-operation\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceID\": \"0000000000000001\",\n          \"spanID\": \"0000000000000002\"\n        }\n      ],\n      \"startTime\": 1485467191639875,\n      \"duration\": 5,\n      \"tags\": [],\n      \"logs\": [],\n      \"processID\": \"p2\",\n      \"warnings\": null\n    },\n    {\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000004\",\n      \"operationName\": \"reference-test\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceID\": \"00000000000000ff\",\n          \"spanID\": \"00000000000000ff\"\n        },\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceID\": \"0000000000000001\",\n          \"spanID\": \"0000000000000002\"\n        },\n        {\n          \"refType\": \"FOLLOWS_FROM\",\n          \"traceID\": \"0000000000000001\",\n          \"spanID\": \"0000000000000002\"\n        }\n      ],\n      \"startTime\": 1485467191639875,\n      \"duration\": 5,\n      \"tags\": [],\n      \"logs\": [],\n      \"processID\": \"p2\",\n      \"warnings\": [\n        \"some span warning\"\n      ]\n    },\n    {\n      \"traceID\": \"0000000000000001\",\n      \"spanID\": \"0000000000000005\",\n      \"operationName\": \"preserveParentID-test\",\n      \"references\": [\n        {\n          \"refType\": \"CHILD_OF\",\n          \"traceID\": \"0000000000000001\",\n          \"spanID\": \"0000000000000004\"\n        }\n      ],\n      \"startTime\": 1485467191639875,\n      \"duration\": 4,\n      \"tags\": [],\n      \"logs\": [],\n      \"processID\": \"p2\",\n      \"warnings\": [\n        \"some span warning\"\n      ]\n    }\n  ],\n  \"processes\": {\n    \"p1\": {\n      \"serviceName\": \"service-x\",\n      \"tags\": []\n    },\n    \"p2\": {\n      \"serviceName\": \"service-y\",\n      \"tags\": []\n    }\n  },\n  \"warnings\": [\n    \"some trace warning\"\n  ]\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/from_domain.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/uimodel\"\n)\n\nconst (\n\tjsMaxSafeInteger = int64(1)<<53 - 1\n\tjsMinSafeInteger = -jsMaxSafeInteger\n)\n\n// FromDomain converts model.Trace into json.Trace format.\n// It assumes that the domain model is valid, namely that all enums\n// have valid values, so that it does not need to check for errors.\nfunc FromDomain(trace *model.Trace) *uimodel.Trace {\n\tfd := fromDomain{}\n\tfd.convertKeyValuesFunc = fd.convertKeyValues\n\treturn fd.fromDomain(trace)\n}\n\n// FromDomainEmbedProcess converts model.Span into json.Span format.\n// This format includes a ParentSpanID and an embedded Process.\nfunc FromDomainEmbedProcess(span *model.Span) *uimodel.Span {\n\tfd := fromDomain{}\n\tfd.convertKeyValuesFunc = fd.convertKeyValuesString\n\treturn fd.convertSpanEmbedProcess(span)\n}\n\ntype fromDomain struct {\n\tconvertKeyValuesFunc func(keyValues model.KeyValues) []uimodel.KeyValue\n}\n\nfunc (fd fromDomain) fromDomain(trace *model.Trace) *uimodel.Trace {\n\tjSpans := make([]uimodel.Span, len(trace.Spans))\n\tprocesses := &processHashtable{}\n\tvar traceID uimodel.TraceID\n\tfor i, span := range trace.Spans {\n\t\tif i == 0 {\n\t\t\ttraceID = uimodel.TraceID(span.TraceID.String())\n\t\t}\n\t\tprocessID := uimodel.ProcessID(processes.getKey(span.Process))\n\t\tjSpans[i] = fd.convertSpan(span, processID)\n\t}\n\tjTrace := &uimodel.Trace{\n\t\tTraceID:   traceID,\n\t\tSpans:     jSpans,\n\t\tProcesses: fd.convertProcesses(processes.getMapping()),\n\t\tWarnings:  trace.Warnings,\n\t}\n\treturn jTrace\n}\n\nfunc (fd fromDomain) convertSpanInternal(span *model.Span) uimodel.Span {\n\treturn uimodel.Span{\n\t\tTraceID:       uimodel.TraceID(span.TraceID.String()),\n\t\tSpanID:        uimodel.SpanID(span.SpanID.String()),\n\t\tFlags:         uint32(span.Flags),\n\t\tOperationName: span.OperationName,\n\t\tStartTime:     model.TimeAsEpochMicroseconds(span.StartTime),\n\t\tDuration:      model.DurationAsMicroseconds(span.Duration),\n\t\tTags:          fd.convertKeyValuesFunc(span.Tags),\n\t\tLogs:          fd.convertLogs(span.Logs),\n\t}\n}\n\nfunc (fd fromDomain) convertSpan(span *model.Span, processID uimodel.ProcessID) uimodel.Span {\n\ts := fd.convertSpanInternal(span)\n\ts.ProcessID = processID\n\ts.Warnings = span.Warnings\n\ts.References = fd.convertReferences(span)\n\treturn s\n}\n\nfunc (fd fromDomain) convertSpanEmbedProcess(span *model.Span) *uimodel.Span {\n\ts := fd.convertSpanInternal(span)\n\tprocess := fd.convertProcess(span.Process)\n\ts.Process = &process\n\ts.References = fd.convertReferences(span)\n\treturn &s\n}\n\nfunc (fd fromDomain) convertReferences(span *model.Span) []uimodel.Reference {\n\tout := make([]uimodel.Reference, 0, len(span.References))\n\tfor _, ref := range span.References {\n\t\tout = append(out, uimodel.Reference{\n\t\t\tRefType: fd.convertRefType(ref.RefType),\n\t\t\tTraceID: uimodel.TraceID(ref.TraceID.String()),\n\t\t\tSpanID:  uimodel.SpanID(ref.SpanID.String()),\n\t\t})\n\t}\n\treturn out\n}\n\nfunc (fromDomain) convertRefType(refType model.SpanRefType) uimodel.ReferenceType {\n\tif refType == model.FollowsFrom {\n\t\treturn uimodel.FollowsFrom\n\t}\n\treturn uimodel.ChildOf\n}\n\nfunc (fromDomain) convertKeyValues(keyValues model.KeyValues) []uimodel.KeyValue {\n\tout := make([]uimodel.KeyValue, len(keyValues))\n\tfor i, kv := range keyValues {\n\t\tvar value any\n\t\tswitch kv.VType {\n\t\tcase model.StringType:\n\t\t\tvalue = kv.VStr\n\t\tcase model.BoolType:\n\t\t\tvalue = kv.Bool()\n\t\tcase model.Int64Type:\n\t\t\tvalue = kv.Int64()\n\t\t\tif kv.Int64() > jsMaxSafeInteger || kv.Int64() < jsMinSafeInteger {\n\t\t\t\tvalue = fmt.Sprintf(\"%d\", value)\n\t\t\t}\n\t\tcase model.Float64Type:\n\t\t\tvalue = kv.Float64()\n\t\tcase model.BinaryType:\n\t\t\tvalue = kv.Binary()\n\t\tdefault:\n\t\t\tvalue = kv.AsString()\n\t\t}\n\n\t\tout[i] = uimodel.KeyValue{\n\t\t\tKey:   kv.Key,\n\t\t\tType:  uimodel.ValueType(strings.ToLower(kv.VType.String())),\n\t\t\tValue: value,\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (fromDomain) convertKeyValuesString(keyValues model.KeyValues) []uimodel.KeyValue {\n\tout := make([]uimodel.KeyValue, len(keyValues))\n\tfor i, kv := range keyValues {\n\t\tout[i] = uimodel.KeyValue{\n\t\t\tKey:   kv.Key,\n\t\t\tType:  uimodel.ValueType(strings.ToLower(kv.VType.String())),\n\t\t\tValue: kv.AsString(),\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (fd fromDomain) convertLogs(logs []model.Log) []uimodel.Log {\n\tout := make([]uimodel.Log, len(logs))\n\tfor i, log := range logs {\n\t\tout[i] = uimodel.Log{\n\t\t\tTimestamp: model.TimeAsEpochMicroseconds(log.Timestamp),\n\t\t\tFields:    fd.convertKeyValuesFunc(log.Fields),\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (fd fromDomain) convertProcesses(processes map[string]*model.Process) map[uimodel.ProcessID]uimodel.Process {\n\tout := make(map[uimodel.ProcessID]uimodel.Process)\n\tfor key, process := range processes {\n\t\tout[uimodel.ProcessID(key)] = fd.convertProcess(process)\n\t}\n\treturn out\n}\n\nfunc (fd fromDomain) convertProcess(process *model.Process) uimodel.Process {\n\treturn uimodel.Process{\n\t\tServiceName: process.ServiceName,\n\t\tTags:        fd.convertKeyValuesFunc(process.Tags),\n\t}\n}\n\n// DependenciesFromDomain converts []model.DependencyLink into []json.DependencyLink format.\nfunc DependenciesFromDomain(dependencyLinks []model.DependencyLink) []uimodel.DependencyLink {\n\tretMe := make([]uimodel.DependencyLink, 0, len(dependencyLinks))\n\tfor _, dependencyLink := range dependencyLinks {\n\t\tretMe = append(\n\t\t\tretMe,\n\t\t\tuimodel.DependencyLink{\n\t\t\t\tParent:    dependencyLink.Parent,\n\t\t\t\tChild:     dependencyLink.Child,\n\t\t\t\tCallCount: dependencyLink.CallCount,\n\t\t\t},\n\t\t)\n\t}\n\treturn retMe\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/from_domain_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n\t\"github.com/jaegertracing/jaeger/internal/uimodel\"\n)\n\nconst NumberOfFixtures = 1\n\nfunc TestMarshalJSON(t *testing.T) {\n\tspan1 := &model.Span{\n\t\tTraceID:       model.TraceID{Low: 1},\n\t\tSpanID:        model.SpanID(2),\n\t\tOperationName: \"span\",\n\t\tStartTime:     time.Now(),\n\t\tDuration:      time.Microsecond,\n\t}\n\ttrace1 := &model.Trace{\n\t\tSpans: []*model.Span{\n\t\t\tspan1,\n\t\t},\n\t\tProcessMap: []model.Trace_ProcessMapping{\n\t\t\t{\n\t\t\t\tProcessID: \"p1\",\n\t\t\t\tProcess: model.Process{\n\t\t\t\t\tServiceName: \"abc\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tm := &jsonpb.Marshaler{}\n\tout := &bytes.Buffer{}\n\n\trequire.NoError(t, m.Marshal(out, trace1))\n\n\tvar trace2 model.Trace\n\tbb := bytes.NewReader(out.Bytes())\n\trequire.NoError(t, jsonpb.Unmarshal(bb, &trace2))\n\ttrace1.NormalizeTimestamps()\n\ttrace2.NormalizeTimestamps()\n\tassert.Equal(t, &trace2, trace1)\n}\n\nfunc TestFromDomain(t *testing.T) {\n\tfor i := 1; i <= NumberOfFixtures; i++ {\n\t\tdomainStr, jsonStr := loadFixturesUI(t, i)\n\n\t\tvar trace model.Trace\n\t\trequire.NoError(t, jsonpb.Unmarshal(bytes.NewReader(domainStr), &trace))\n\t\tuiTrace := FromDomain(&trace)\n\n\t\ttestJSONEncoding(t, i, jsonStr, uiTrace, false)\n\t}\n}\n\nfunc TestFromDomainEmbedProcess(t *testing.T) {\n\tfor i := 1; i <= NumberOfFixtures; i++ {\n\t\tdomainStr, jsonStr := loadFixturesES(t, i)\n\n\t\tvar span model.Span\n\t\trequire.NoError(t, jsonpb.Unmarshal(bytes.NewReader(domainStr), &span))\n\t\tembeddedSpan := FromDomainEmbedProcess(&span)\n\n\t\tvar expectedSpan uimodel.Span\n\t\trequire.NoError(t, json.Unmarshal(jsonStr, &expectedSpan))\n\n\t\ttestJSONEncoding(t, i, jsonStr, embeddedSpan, true)\n\n\t\tCompareJSONSpans(t, &expectedSpan, embeddedSpan)\n\t}\n}\n\nfunc loadFixturesUI(t *testing.T, i int) (inStr []byte, outStr []byte) {\n\treturn loadFixtures(t, i, false)\n}\n\nfunc loadFixturesES(t *testing.T, i int) (inStr []byte, outStr []byte) {\n\treturn loadFixtures(t, i, true)\n}\n\n// Loads and returns domain model and JSON model fixtures with given number i.\nfunc loadFixtures(t *testing.T, i int, processEmbedded bool) (inStr []byte, outStr []byte) {\n\tvar in string\n\tvar err error\n\tif processEmbedded {\n\t\tin = fmt.Sprintf(\"fixtures/domain_es_%02d.json\", i)\n\t} else {\n\t\tin = fmt.Sprintf(\"fixtures/domain_%02d.json\", i)\n\t}\n\tinStr, err = os.ReadFile(in)\n\trequire.NoError(t, err)\n\tvar out string\n\tif processEmbedded {\n\t\tout = fmt.Sprintf(\"fixtures/es_%02d.json\", i)\n\t} else {\n\t\tout = fmt.Sprintf(\"fixtures/ui_%02d.json\", i)\n\t}\n\toutStr, err = os.ReadFile(out)\n\trequire.NoError(t, err)\n\treturn inStr, outStr\n}\n\nfunc testJSONEncoding(t *testing.T, i int, expectedStr []byte, object any, processEmbedded bool) {\n\tbuf := &bytes.Buffer{}\n\tenc := json.NewEncoder(buf)\n\tenc.SetIndent(\"\", \"  \")\n\n\tvar outFile string\n\tif processEmbedded {\n\t\toutFile = fmt.Sprintf(\"fixtures/es_%02d\", i)\n\t} else {\n\t\toutFile = fmt.Sprintf(\"fixtures/ui_%02d\", i)\n\t}\n\trequire.NoError(t, enc.Encode(object))\n\n\tif !assert.Equal(t, string(expectedStr), buf.String()) {\n\t\terr := os.WriteFile(outFile+\"-actual.json\", buf.Bytes(), 0o644)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc TestDependenciesFromDomain(t *testing.T) {\n\tsomeParent := \"someParent\"\n\tsomeChild := \"someChild\"\n\tsomeCallCount := uint64(123)\n\tanotherParent := \"anotherParent\"\n\tanotherChild := \"anotherChild\"\n\tanotherCallCount := uint64(456)\n\texpected := []uimodel.DependencyLink{\n\t\t{\n\t\t\tParent:    someParent,\n\t\t\tChild:     someChild,\n\t\t\tCallCount: someCallCount,\n\t\t},\n\t\t{\n\t\t\tParent:    anotherParent,\n\t\t\tChild:     anotherChild,\n\t\t\tCallCount: anotherCallCount,\n\t\t},\n\t}\n\tinput := []model.DependencyLink{\n\t\t{\n\t\t\tParent:    someParent,\n\t\t\tChild:     someChild,\n\t\t\tCallCount: someCallCount,\n\t\t},\n\t\t{\n\t\t\tParent:    anotherParent,\n\t\t\tChild:     anotherChild,\n\t\t\tCallCount: anotherCallCount,\n\t\t},\n\t}\n\tactual := DependenciesFromDomain(input)\n\tassert.Equal(t, expected, actual)\n}\n\nfunc TestConvertKeyValues_DefaultValueType(t *testing.T) {\n\t// Create a custom ValueType that's not handled by the switch\n\tcustomType := model.ValueType(999)\n\n\tkv := model.KeyValue{\n\t\tKey:   \"custom-key\",\n\t\tVType: customType,\n\t\tVStr:  \"custom-value\",\n\t}\n\n\tfd := fromDomain{}\n\tresult := fd.convertKeyValues(model.KeyValues{kv})\n\n\trequire.Len(t, result, 1)\n\tassert.Equal(t, \"custom-key\", result[0].Key)\n\n\tassert.Equal(t, \"unknown type 999\", result[0].Value)\n\tassert.Equal(t, uimodel.ValueType(\"999\"), result[0].Type)\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/json_span_compare_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tesjson \"github.com/jaegertracing/jaeger/internal/uimodel\"\n)\n\nfunc CompareJSONSpans(t *testing.T, expected *esjson.Span, actual *esjson.Span) {\n\tsortJSONSpan(expected)\n\tsortJSONSpan(actual)\n\n\tif !assert.Equal(t, expected, actual) {\n\t\tfor _, err := range pretty.Diff(expected, actual) {\n\t\t\tt.Log(err)\n\t\t}\n\t\tout, err := json.Marshal(actual)\n\t\trequire.NoError(t, err)\n\t\tt.Logf(\"Actual trace: %s\", string(out))\n\t}\n}\n\nfunc sortJSONSpan(span *esjson.Span) {\n\tsortJSONTags(span.Tags)\n\tsortJSONLogs(span.Logs)\n\tsortJSONProcess(span.Process)\n}\n\ntype JSONTagByKey []esjson.KeyValue\n\nfunc (t JSONTagByKey) Len() int           { return len(t) }\nfunc (t JSONTagByKey) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }\nfunc (t JSONTagByKey) Less(i, j int) bool { return t[i].Key < t[j].Key }\n\nfunc sortJSONTags(tags []esjson.KeyValue) {\n\tsort.Sort(JSONTagByKey(tags))\n}\n\ntype JSONLogByTimestamp []esjson.Log\n\nfunc (t JSONLogByTimestamp) Len() int           { return len(t) }\nfunc (t JSONLogByTimestamp) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }\nfunc (t JSONLogByTimestamp) Less(i, j int) bool { return t[i].Timestamp < t[j].Timestamp }\n\nfunc sortJSONLogs(logs []esjson.Log) {\n\tsort.Sort(JSONLogByTimestamp(logs))\n\tfor i := range logs {\n\t\tsortJSONTags(logs[i].Fields)\n\t}\n}\n\nfunc sortJSONProcess(process *esjson.Process) {\n\tsortJSONTags(process.Tags)\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/package_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/process_hashtable.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\ntype processHashtable struct {\n\tcount     int\n\tprocesses map[uint64][]processKey\n\textHash   func(*model.Process) uint64\n}\n\ntype processKey struct {\n\tprocess *model.Process\n\tkey     string\n}\n\n// getKey assigns a new unique string key to the process, or returns\n// a previously assigned value if the process has already been seen.\nfunc (ph *processHashtable) getKey(process *model.Process) string {\n\tif ph.processes == nil {\n\t\tph.processes = make(map[uint64][]processKey)\n\t}\n\thash := ph.hash(process)\n\tif keys, ok := ph.processes[hash]; ok {\n\t\tfor _, k := range keys {\n\t\t\tif k.process.Equal(process) {\n\t\t\t\treturn k.key\n\t\t\t}\n\t\t}\n\t\tkey := ph.nextKey()\n\t\tkeys = append(keys, processKey{process: process, key: key})\n\t\tph.processes[hash] = keys\n\t\treturn key\n\t}\n\tkey := ph.nextKey()\n\tph.processes[hash] = []processKey{{process: process, key: key}}\n\treturn key\n}\n\n// getMapping returns the accumulated mapping of string keys to processes.\nfunc (ph *processHashtable) getMapping() map[string]*model.Process {\n\tout := make(map[string]*model.Process)\n\tfor _, keys := range ph.processes {\n\t\tfor _, key := range keys {\n\t\t\tout[key.key] = key.process\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (ph *processHashtable) nextKey() string {\n\tph.count++\n\tkey := \"p\" + strconv.Itoa(ph.count)\n\treturn key\n}\n\nfunc (ph processHashtable) hash(process *model.Process) uint64 {\n\tif ph.extHash != nil {\n\t\t// for testing collisions\n\t\treturn ph.extHash(process)\n\t}\n\thc, _ := model.HashCode(process)\n\treturn hc\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/process_hashtable_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger-idl/model/v1\"\n)\n\nfunc TestProcessHashtable(t *testing.T) {\n\tht := &processHashtable{}\n\n\tp1 := model.NewProcess(\"s1\", []model.KeyValue{\n\t\tmodel.String(\"ip\", \"1.2.3.4\"),\n\t\tmodel.String(\"host\", \"google.com\"),\n\t})\n\t// same process but with different order of tags\n\tp1dup := model.NewProcess(\"s1\", []model.KeyValue{\n\t\tmodel.String(\"host\", \"google.com\"),\n\t\tmodel.String(\"ip\", \"1.2.3.4\"),\n\t})\n\tp2 := model.NewProcess(\"s2\", []model.KeyValue{\n\t\tmodel.String(\"host\", \"facebook.com\"),\n\t})\n\n\tassert.Equal(t, \"p1\", ht.getKey(p1))\n\tassert.Equal(t, \"p1\", ht.getKey(p1))\n\tassert.Equal(t, \"p1\", ht.getKey(p1dup))\n\tassert.Equal(t, \"p2\", ht.getKey(p2))\n\n\texpectedMapping := map[string]*model.Process{\n\t\t\"p1\": p1,\n\t\t\"p2\": p2,\n\t}\n\tassert.Equal(t, expectedMapping, ht.getMapping())\n}\n\nfunc TestProcessHashtableCollision(t *testing.T) {\n\tht := &processHashtable{}\n\t// hash all processes to the same number\n\tht.extHash = func(*model.Process) uint64 {\n\t\treturn 42\n\t}\n\n\tp1 := model.NewProcess(\"s1\", []model.KeyValue{\n\t\tmodel.String(\"host\", \"google.com\"),\n\t})\n\tp2 := model.NewProcess(\"s2\", []model.KeyValue{\n\t\tmodel.String(\"host\", \"facebook.com\"),\n\t})\n\tassert.Equal(t, \"p1\", ht.getKey(p1))\n\tassert.Equal(t, \"p2\", ht.getKey(p2))\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/sampling.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\n\t\"github.com/gogo/protobuf/jsonpb\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n)\n\n// SamplingStrategyResponseToJSON defines the official way to generate\n// a JSON response from /sampling endpoints.\nfunc SamplingStrategyResponseToJSON(protoObj *api_v2.SamplingStrategyResponse) (string, error) {\n\t// For backwards compatibility with Thrift-to-JSON encoding,\n\t// we want the output to include \"strategyType\":\"PROBABILISTIC\" when appropriate.\n\t// However, due to design oversight, the enum value for PROBABILISTIC is 0, so\n\t// we need to set EmitDefaults=true. This in turns causes null fields to be emitted too,\n\t// so we take care of them below.\n\tjsonpbMarshaler := jsonpb.Marshaler{\n\t\tEmitDefaults: true,\n\t}\n\n\tstr, err := jsonpbMarshaler.MarshalToString(protoObj)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Because we set EmitDefaults, jsonpb will also render null entries, so we remove them here.\n\tstr = strings.ReplaceAll(str, `\"probabilisticSampling\":null,`, \"\")\n\tstr = strings.ReplaceAll(str, `,\"rateLimitingSampling\":null`, \"\")\n\tstr = strings.ReplaceAll(str, `,\"operationSampling\":null`, \"\")\n\n\treturn str, nil\n}\n\n// SamplingStrategyResponseFromJSON is the official way to parse strategy in JSON.\nfunc SamplingStrategyResponseFromJSON(json []byte) (*api_v2.SamplingStrategyResponse, error) {\n\tvar obj api_v2.SamplingStrategyResponse\n\tif err := jsonpb.Unmarshal(bytes.NewReader(json), &obj); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &obj, nil\n}\n"
  },
  {
    "path": "internal/uimodel/converter/v1/json/sampling_test.go",
    "content": "// Copyright (c) 2023 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage json\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/jaegertracing/jaeger-idl/proto-gen/api_v2\"\n\tapiv1 \"github.com/jaegertracing/jaeger-idl/thrift-gen/sampling\"\n\tthriftconv \"github.com/jaegertracing/jaeger/internal/converter/thrift/jaeger\"\n)\n\nfunc TestSamplingStrategyResponseToJSON_Error(t *testing.T) {\n\t_, err := SamplingStrategyResponseToJSON(nil)\n\trequire.Error(t, err)\n}\n\n// TestSamplingStrategyResponseToJSON verifies that the function outputs\n// the same string as Thrift-based JSON marshaler.\nfunc TestSamplingStrategyResponseToJSON(t *testing.T) {\n\tt.Run(\"probabilistic\", func(t *testing.T) {\n\t\ts := &apiv1.SamplingStrategyResponse{\n\t\t\tStrategyType: apiv1.SamplingStrategyType_PROBABILISTIC,\n\t\t\tProbabilisticSampling: &apiv1.ProbabilisticSamplingStrategy{\n\t\t\t\tSamplingRate: 0.42,\n\t\t\t},\n\t\t}\n\t\tcompareProtoAndThriftJSON(t, s)\n\t})\n\tt.Run(\"rateLimiting\", func(t *testing.T) {\n\t\ts := &apiv1.SamplingStrategyResponse{\n\t\t\tStrategyType: apiv1.SamplingStrategyType_RATE_LIMITING,\n\t\t\tRateLimitingSampling: &apiv1.RateLimitingSamplingStrategy{\n\t\t\t\tMaxTracesPerSecond: 42,\n\t\t\t},\n\t\t}\n\t\tcompareProtoAndThriftJSON(t, s)\n\t})\n\tt.Run(\"operationSampling\", func(t *testing.T) {\n\t\ta := 11.2 // we need a pointer to value\n\t\ts := &apiv1.SamplingStrategyResponse{\n\t\t\tOperationSampling: &apiv1.PerOperationSamplingStrategies{\n\t\t\t\tDefaultSamplingProbability:       0.42,\n\t\t\t\tDefaultUpperBoundTracesPerSecond: &a,\n\t\t\t\tDefaultLowerBoundTracesPerSecond: 2,\n\t\t\t\tPerOperationStrategies: []*apiv1.OperationSamplingStrategy{\n\t\t\t\t\t{\n\t\t\t\t\t\tOperation: \"foo\",\n\t\t\t\t\t\tProbabilisticSampling: &apiv1.ProbabilisticSamplingStrategy{\n\t\t\t\t\t\t\tSamplingRate: 0.42,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tOperation: \"bar\",\n\t\t\t\t\t\tProbabilisticSampling: &apiv1.ProbabilisticSamplingStrategy{\n\t\t\t\t\t\t\tSamplingRate: 0.42,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tcompareProtoAndThriftJSON(t, s)\n\t})\n}\n\nfunc compareProtoAndThriftJSON(t *testing.T, thriftObj *apiv1.SamplingStrategyResponse) {\n\tprotoObj, err := thriftconv.ConvertSamplingResponseToDomain(thriftObj)\n\trequire.NoError(t, err)\n\n\ts1, err := json.Marshal(thriftObj)\n\trequire.NoError(t, err)\n\n\ts2, err := SamplingStrategyResponseToJSON(protoObj)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, string(s1), s2)\n}\n\nfunc TestSamplingStrategyResponseFromJSON(t *testing.T) {\n\t_, err := SamplingStrategyResponseFromJSON([]byte(\"broken\"))\n\trequire.Error(t, err)\n\n\ts1 := &api_v2.SamplingStrategyResponse{\n\t\tStrategyType: api_v2.SamplingStrategyType_PROBABILISTIC,\n\t\tProbabilisticSampling: &api_v2.ProbabilisticSamplingStrategy{\n\t\t\tSamplingRate: 0.42,\n\t\t},\n\t}\n\tjsonData, err := SamplingStrategyResponseToJSON(s1)\n\trequire.NoError(t, err)\n\n\ts2, err := SamplingStrategyResponseFromJSON([]byte(jsonData))\n\trequire.NoError(t, err)\n\tassert.Equal(t, s1.GetStrategyType(), s2.GetStrategyType())\n\tassert.Equal(t, s1.GetProbabilisticSampling(), s2.GetProbabilisticSampling())\n}\n"
  },
  {
    "path": "internal/uimodel/doc.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package json defines the external JSON representation for Jaeger traces.\npackage uimodel\n"
  },
  {
    "path": "internal/uimodel/empty_test.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uimodel\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "internal/uimodel/model.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// Copyright (c) 2017 Uber Technologies, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\npackage uimodel\n\n// ReferenceType is the reference type of one span to another\ntype ReferenceType string\n\n// TraceID is the shared trace ID of all spans in the trace.\ntype TraceID string\n\n// SpanID is the id of a span\ntype SpanID string\n\n// ProcessID is a hashed value of the Process struct that is unique within the trace.\ntype ProcessID string\n\n// ValueType is the type of a value stored in KeyValue struct.\ntype ValueType string\n\nconst (\n\t// ChildOf means a span is the child of another span\n\tChildOf ReferenceType = \"CHILD_OF\"\n\t// FollowsFrom means a span follows from another span\n\tFollowsFrom ReferenceType = \"FOLLOWS_FROM\"\n\n\t// StringType indicates a string value stored in KeyValue\n\tStringType ValueType = \"string\"\n\t// BoolType indicates a Boolean value stored in KeyValue\n\tBoolType ValueType = \"bool\"\n\t// Int64Type indicates a 64bit signed integer value stored in KeyValue\n\tInt64Type ValueType = \"int64\"\n\t// Float64Type indicates a 64bit float value stored in KeyValue\n\tFloat64Type ValueType = \"float64\"\n\t// BinaryType indicates an arbitrary byte array stored in KeyValue\n\tBinaryType ValueType = \"binary\"\n)\n\n// Trace is a list of spans\ntype Trace struct {\n\tTraceID   TraceID               `json:\"traceID\"`\n\tSpans     []Span                `json:\"spans\"`\n\tProcesses map[ProcessID]Process `json:\"processes\"`\n\tWarnings  []string              `json:\"warnings\"`\n}\n\n// Span is a span denoting a piece of work in some infrastructure\n// When converting to UI model, ParentSpanID and Process should be dereferenced into\n// References and ProcessID, respectively.\n// When converting to ES model, ProcessID and Warnings should be omitted. Even if\n// included, ES with dynamic settings off will automatically ignore unneeded fields.\ntype Span struct {\n\tTraceID       TraceID     `json:\"traceID\"`\n\tSpanID        SpanID      `json:\"spanID\"`\n\tParentSpanID  SpanID      `json:\"parentSpanID,omitempty\"` // deprecated\n\tFlags         uint32      `json:\"flags,omitempty\"`\n\tOperationName string      `json:\"operationName\"`\n\tReferences    []Reference `json:\"references\"`\n\tStartTime     uint64      `json:\"startTime\"` // microseconds since Unix epoch\n\tDuration      uint64      `json:\"duration\"`  // microseconds\n\tTags          []KeyValue  `json:\"tags\"`\n\tLogs          []Log       `json:\"logs\"`\n\tProcessID     ProcessID   `json:\"processID,omitempty\"`\n\tProcess       *Process    `json:\"process,omitempty\"`\n\tWarnings      []string    `json:\"warnings\"`\n}\n\n// Reference is a reference from one span to another\ntype Reference struct {\n\tRefType ReferenceType `json:\"refType\"`\n\tTraceID TraceID       `json:\"traceID\"`\n\tSpanID  SpanID        `json:\"spanID\"`\n}\n\n// Process is the process emitting a set of spans\ntype Process struct {\n\tServiceName string     `json:\"serviceName\"`\n\tTags        []KeyValue `json:\"tags\"`\n}\n\n// Log is a log emitted in a span\ntype Log struct {\n\tTimestamp uint64     `json:\"timestamp\"`\n\tFields    []KeyValue `json:\"fields\"`\n}\n\n// KeyValue is a key-value pair with typed value.\ntype KeyValue struct {\n\tKey   string    `json:\"key\"`\n\tType  ValueType `json:\"type,omitempty\"`\n\tValue any       `json:\"value\"`\n}\n\n// DependencyLink shows dependencies between services\ntype DependencyLink struct {\n\tParent    string `json:\"parent\"`\n\tChild     string `json:\"child\"`\n\tCallCount uint64 `json:\"callCount\"`\n}\n\n// Operation defines the data in the operation response when query operation by service and span kind\ntype Operation struct {\n\tName     string `json:\"name\"`\n\tSpanKind string `json:\"spanKind\"`\n}\n"
  },
  {
    "path": "internal/version/build.go",
    "content": "// Copyright (c) 2017 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/jaegertracing/jaeger/internal/metrics\"\n)\n\nvar (\n\t// commitFromGit is a constant representing the source version that\n\t// generated this build. It should be set during build via -ldflags.\n\tcommitSHA string\n\t// versionFromGit is a constant representing the version tag that\n\t// generated this build. It should be set during build via -ldflags.\n\tlatestVersion string\n\t// build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n\tdate string\n)\n\n// Info holds build information.\ntype Info struct {\n\tGitCommit  string `json:\"gitCommit\"`\n\tGitVersion string `json:\"gitVersion\"`\n\tBuildDate  string `json:\"buildDate\"`\n}\n\n// InfoMetrics hold a gauge whose tags include build information.\ntype InfoMetrics struct {\n\tBuildInfo metrics.Gauge `metric:\"build_info\"`\n}\n\n// Get creates and initialized Info object\nfunc Get() Info {\n\treturn Info{\n\t\tGitCommit:  commitSHA,\n\t\tGitVersion: latestVersion,\n\t\tBuildDate:  date,\n\t}\n}\n\n// NewInfoMetrics returns a InfoMetrics\nfunc NewInfoMetrics(metricsFactory metrics.Factory) *InfoMetrics {\n\tvar info InfoMetrics\n\n\tbuildTags := map[string]string{\n\t\t\"revision\":   commitSHA,\n\t\t\"version\":    latestVersion,\n\t\t\"build_date\": date,\n\t}\n\tmetrics.Init(&info, metricsFactory, buildTags)\n\tinfo.BuildInfo.Update(1)\n\n\treturn &info\n}\n\nfunc (i Info) String() string {\n\treturn fmt.Sprintf(\n\t\t\"git-commit=%s, git-version=%s, build-date=%s\",\n\t\ti.GitCommit, i.GitVersion, i.BuildDate,\n\t)\n}\n"
  },
  {
    "path": "internal/version/build_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGet(t *testing.T) {\n\tcommitSHA = \"foobar\"\n\tlatestVersion = \"v1.2.3\"\n\tdate = \"2024-01-04\"\n\n\tinfo := Get()\n\n\tassert.Equal(t, commitSHA, info.GitCommit)\n\tassert.Equal(t, latestVersion, info.GitVersion)\n\tassert.Equal(t, date, info.BuildDate)\n}\n\nfunc TestString(t *testing.T) {\n\tcommitSHA = \"foobar\"\n\tlatestVersion = \"v1.2.3\"\n\tdate = \"2024-01-04\"\n\ttest := Info{\n\t\tGitCommit:  commitSHA,\n\t\tGitVersion: latestVersion,\n\t\tBuildDate:  date,\n\t}\n\texpectedOutput := \"git-commit=foobar, git-version=v1.2.3, build-date=2024-01-04\"\n\tassert.Equal(t, expectedOutput, test.String())\n}\n"
  },
  {
    "path": "internal/version/command.go",
    "content": "// Copyright (c) 2017 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// Command creates version command\nfunc Command() *cobra.Command {\n\tinfo := Get()\n\tlog.Println(\"application version:\", info)\n\treturn &cobra.Command{\n\t\tUse:   \"version\",\n\t\tShort: \"Print the version.\",\n\t\tLong:  `Print the version and build information.`,\n\t\tRunE: func(cmd *cobra.Command, _ /* args */ []string) error {\n\t\t\tjson, err := json.Marshal(info)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfmt.Fprint(cmd.OutOrStdout(), string(json))\n\t\t\treturn nil\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "internal/version/command_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewCommand(t *testing.T) {\n\tcommitSHA = \"foobar\"\n\tlatestVersion = \"v1.2.3\"\n\tdate = \"2024-01-04\"\n\tcmd := Command()\n\n\tvar b bytes.Buffer\n\tcmd.SetOut(&b)\n\terr := cmd.Execute()\n\trequire.NoError(t, err)\n\tout, err := io.ReadAll(&b)\n\trequire.NoError(t, err)\n\texpectedCommandOutput := `{\"gitCommit\":\"foobar\",\"gitVersion\":\"v1.2.3\",\"buildDate\":\"2024-01-04\"}`\n\tassert.Equal(t, expectedCommandOutput, string(out))\n}\n"
  },
  {
    "path": "internal/version/handler.go",
    "content": "// Copyright (c) 2017 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"go.uber.org/zap\"\n)\n\n// RegisterHandler registers version handler to /version\nfunc RegisterHandler(mu *http.ServeMux, logger *zap.Logger) {\n\tinfo := Get()\n\tjsonData, err := json.Marshal(info)\n\tif err != nil {\n\t\tlogger.Fatal(\"Could not get Jaeger version\", zap.Error(err))\n\t}\n\tmu.HandleFunc(\"/version\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Write(jsonData)\n\t})\n}\n"
  },
  {
    "path": "internal/version/handler_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n)\n\nfunc TestRegisterHandler(t *testing.T) {\n\tcommitSHA = \"foobar\"\n\tlatestVersion = \"v1.2.3\"\n\tdate = \"2024-01-04\"\n\texpectedJSON := `{\"gitCommit\":\"foobar\",\"gitVersion\":\"v1.2.3\",\"buildDate\":\"2024-01-04\"}`\n\n\tmockLogger := zap.NewNop()\n\tmux := http.NewServeMux()\n\tRegisterHandler(mux, mockLogger)\n\tserver := httptest.NewServer(mux)\n\n\tdefer server.Close()\n\n\tresp, err := http.Get(server.URL + \"/version\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\tbody, err := io.ReadAll(resp.Body)\n\trequire.NoError(t, err)\n\tresp.Body.Close()\n\tassert.JSONEq(t, expectedJSON, string(body))\n}\n"
  },
  {
    "path": "internal/version/package_test.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage version\n\nimport (\n\t\"testing\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "monitoring/jaeger-mixin/README.md",
    "content": "# Prometheus monitoring mixin for Jaeger\n\nThe Prometheus monitoring mixin for Jaeger provides a starting point for people wanting to monitor Jaeger using Prometheus, Alertmanager, and Grafana. To use it, you'll need [`jsonnet`](https://github.com/google/go-jsonnet) and [`jb` (jsonnet-bundler)](https://github.com/jsonnet-bundler/jsonnet-bundler). They can be installed using `go get`, as follows:\n\n```console\nmkdir -p ~/bin && curl -sL https://github.com/google/go-jsonnet/releases/download/v0.17.0/go-jsonnet_0.17.0_Linux_x86_64.tar.gz | tar -C ~/bin/ -xzf -\ncurl -sLo ~/bin/jb https://github.com/jsonnet-bundler/jsonnet-bundler/releases/download/v0.4.0/jb-linux-amd64\nchmod +x ~/bin/jb\n```\n\nYour monitoring mixin can then be initialized as follows:\n\n```console\njb init\njb install \\\n  github.com/jaegertracing/jaeger/monitoring/jaeger-mixin@main \\\n  github.com/grafana/jsonnet-libs/grafana-builder@master \\\n  github.com/coreos/kube-prometheus/jsonnet/kube-prometheus@main\n```\n\nIn the directory where your mixin was initialized, create a new `monitoring-setup.jsonnet`, specifying how your monitoring stack should look like: this file is yours, any customizations to Prometheus, Grafana, or Alertmanager should take place here. A simple example providing only the Jaeger dashboard for Grafana would be:\n\n```jsonnet\nlocal jaegerDashboard = (import 'jaeger-mixin/mixin.libsonnet').grafanaDashboards;\n{ ['dashboards-jaeger.json']: jaegerDashboard['jaeger.json'] }\n```\n\nThe manifest files can be generated via the `jsonnet` command below. Once the command finishes, the file `manifests/dashboards-jaeger.json` should be available and can be loaded directly into Grafana.\n\n```console\njsonnet -J vendor -cm manifests/ monitoring-setup.jsonnet\n```\n\nAn example producing the manifests for a complete monitoring stack is located in this directory, as `monitoring-setup.example.jsonnet`. The manifests include Prometheus, Grafana, and Alertmanager managed via the Prometheus Operator for Kubernetes.\n\n```jsonnet\nlocal jaegerAlerts = (import 'jaeger-mixin/alerts.libsonnet').prometheusAlerts;\nlocal jaegerDashboard = (import 'jaeger-mixin/mixin.libsonnet').grafanaDashboards;\n\nlocal kp =\n  (import 'kube-prometheus/main.libsonnet') +\n  {\n    values+:: {\n      common+: {\n        namespace: 'observability',\n      },\n      grafana+: {\n        dashboards+:: {\n          'my-dashboard.json': jaegerDashboard['jaeger.json'],\n        },\n      },\n    },\n    exampleApplication: {\n      prometheusRuleExample: {\n        apiVersion: 'monitoring.coreos.com/v1',\n        kind: 'PrometheusRule',\n        metadata: {\n          name: 'my-prometheus-rule',\n          namespace: $.values.common.namespace,\n        },\n        spec: jaegerAlerts,\n      },\n    },\n  };\n{ ['00namespace-' + name + '.json']: kp.kubePrometheus[name] for name in std.objectFields(kp.kubePrometheus) } +\n{ ['0prometheus-operator-' + name + '.json']: kp.prometheusOperator[name] for name in std.objectFields(kp.prometheusOperator) } +\n{ ['node-exporter-' + name + '.json']: kp.nodeExporter[name] for name in std.objectFields(kp.nodeExporter) } +\n{ ['kube-state-metrics-' + name + '.json']: kp.kubeStateMetrics[name] for name in std.objectFields(kp.kubeStateMetrics) } +\n{ ['alertmanager-' + name + '.json']: kp.alertmanager[name] for name in std.objectFields(kp.alertmanager) } +\n{ ['prometheus-' + name + '.json']: kp.prometheus[name] for name in std.objectFields(kp.prometheus) } +\n{ ['prometheus-adapter-' + name + '.json']: kp.prometheusAdapter[name] for name in std.objectFields(kp.prometheusAdapter) } +\n{ ['grafana-' + name + '.json']: kp.grafana[name] for name in std.objectFields(kp.grafana) } +\n{ ['my-application-' + name + '.json']: kp.exampleApplication[name] for name in std.objectFields(kp.exampleApplication) }\n```\n\nThe manifest files can be generated via `jsonnet` and passed directly to `kubectl`:\n\n```console\njsonnet -J vendor -cm manifests/ monitoring-setup.jsonnet\nkubectl apply -f manifests/\n```\n\nThe resulting manifests will include everything that is needed to have a Prometheus, Alertmanager, and Grafana instances. Whenever a new alert rule is needed, or a new dashboard has to be defined, change your `monitoring-setup.jsonnet`, re-generate and re-apply the manifests.\n\nMake sure your Prometheus setup is properly scraping the Jaeger components, either by creating a `ServiceMonitor` (and the backing `Service` objects), or via `PodMonitor` resources, like:\n\n```console\nkubectl apply -f - <<EOF\napiVersion: monitoring.coreos.com/v1\nkind: PodMonitor\nmetadata:\n  name: tracing\n  namespace: observability\nspec:\n  podMetricsEndpoints:\n  - interval: 5s\n    targetPort: 14269\n  selector:\n    matchLabels:\n      app: jaeger\nEOF\n```\n\nThis `PodMonitor` tells Prometheus to scrape the port `14269` from all pods containing the label `app: jaeger`. If you have the Jaeger Collector, Agent, and Query in different pods, you might need to adjust or create further `PodMonitor` resources to scrape metrics from the other ports.\n\nThis mixin was originally developed by [Grafana Labs](https://github.com/grafana/jsonnet-libs/tree/master/jaeger-mixin).\n\n## Pre-built dashboard and alert rules\n\nThis repository contains also a pre-built dashboard for Grafana and alert rules for Alertmanager. While we recommend that you generate those resources following the steps above, the following files can be used for quick tests.\n\n- [Dashboard](./dashboard-for-grafana.json)\n- [Alerts](./prometheus_alerts.yml)\n\n_IMPORTANT_: the metrics that are used by default by the dashboard are compatible with the components deployed as part of the production strategy, where each component is deployed individually. Some metric names differ from the ones used in the all-in-one strategy. Adjust your dashboard to reflect your scenario.\n\n## Background\n\n* For more information about monitoring mixins, see this [design doc](https://docs.google.com/document/d/1A9xvzwqnFVSOZ5fD3blKODXfsat5fg6ZhnKu9LK3lB4/view).\n"
  },
  {
    "path": "monitoring/jaeger-mixin/alerts.libsonnet",
    "content": "local percentErrs(metric, errSelectors) = '100 * sum(rate(%(metric)s{%(errSelectors)s}[1m])) by (instance, job, namespace) / sum(rate(%(metric)s[1m])) by (instance, job, namespace)' % {\n  metric: metric,\n  errSelectors: errSelectors,\n};\n\nlocal percentErrsWithTotal(metric_errs, metric_total) = '100 * sum(rate(%(metric_errs)s[1m])) by (instance, job, namespace) / sum(rate(%(metric_total)s[1m])) by (instance, job, namespace)' % {\n  metric_errs: metric_errs,\n  metric_total: metric_total,\n};\n\n{\n  prometheusAlerts+:: {\n    groups+: [\n      {\n        name: 'jaeger_alerts',\n        rules: [{\n          alert: 'JaegerHTTPServerErrs',\n          expr: percentErrsWithTotal('jaeger_agent_http_server_errors_total', 'jaeger_agent_http_server_total') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              {{ $labels.job }} {{ $labels.instance }} is experiencing {{ printf \"%.2f\" $value }}% HTTP errors.\n            |||,\n          },\n        }, {\n          alert: 'JaegerRPCRequestsErrors',\n          expr: percentErrs('jaeger_client_jaeger_rpc_http_requests', 'status_code=~\"4xx|5xx\"') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              {{ $labels.job }} {{ $labels.instance }} is experiencing {{ printf \"%.2f\" $value }}% RPC HTTP errors.\n            |||,\n          },\n        }, {\n          alert: 'JaegerClientSpansDropped',\n          expr: percentErrs('jaeger_reporter_spans', 'result=~\"dropped|err\"') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              service {{ $labels.job }} {{ $labels.instance }} is dropping {{ printf \"%.2f\" $value }}% spans.\n            |||,\n          },\n        }, {\n          alert: 'JaegerAgentSpansDropped',\n          expr: percentErrsWithTotal('jaeger_agent_reporter_batches_failures_total', 'jaeger_agent_reporter_batches_submitted_total') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              agent {{ $labels.job }} {{ $labels.instance }} is dropping {{ printf \"%.2f\" $value }}% spans.\n            |||,\n          },\n        }, {\n          alert: 'JaegerCollectorDroppingSpans',\n          expr: percentErrsWithTotal('jaeger_collector_spans_dropped_total', 'jaeger_collector_spans_received_total') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              collector {{ $labels.job }} {{ $labels.instance }} is dropping {{ printf \"%.2f\" $value }}% spans.\n            |||,\n          },\n        }, {\n          alert: 'JaegerSamplingUpdateFailing',\n          expr: percentErrs('jaeger_sampler_queries', 'result=\"err\"') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              {{ $labels.job }} {{ $labels.instance }} is failing {{ printf \"%.2f\" $value }}% in updating sampling policies.\n            |||,\n          },\n        }, {\n          alert: 'JaegerThrottlingUpdateFailing',\n          expr: percentErrs('jaeger_throttler_updates', 'result=\"err\"') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              {{ $labels.job }} {{ $labels.instance }} is failing {{ printf \"%.2f\" $value }}% in updating throttling policies.\n            |||,\n          },\n        }, {\n          alert: 'JaegerQueryReqsFailing',\n          expr: percentErrs('jaeger_query_requests_total', 'result=\"err\"') + '> 1',\n          'for': '15m',\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            message: |||\n              {{ $labels.job }} {{ $labels.instance }} is seeing {{ printf \"%.2f\" $value }}% query errors on {{ $labels.operation }}.\n            |||,\n          },\n        }],\n      },\n    ],\n  },\n}\n"
  },
  {
    "path": "monitoring/jaeger-mixin/dashboard-for-grafana.json",
    "content": "{\n   \"annotations\": {\n      \"list\": [ ]\n   },\n   \"editable\": true,\n   \"gnetId\": null,\n   \"graphTooltip\": 0,\n   \"hideControls\": false,\n   \"links\": [ ],\n   \"refresh\": \"10s\",\n   \"rows\": [\n      {\n         \"collapse\": false,\n         \"height\": \"250px\",\n         \"panels\": [\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 1,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"sum(rate(otelcol_receiver_refused_spans_total[1m])) or vector(0)\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"error\",\n                     \"legendLink\": null\n                  },\n                  {\n                     \"expr\": \"sum(rate(otelcol_receiver_accepted_spans_total[1m]))\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"success\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Span Ingest Rate\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            },\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 2,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"sum(rate(otelcol_receiver_refused_spans_total[1m])) by (receiver, transport) / (sum(rate(otelcol_receiver_accepted_spans_total[1m])) by (receiver, transport) + sum(rate(otelcol_receiver_refused_spans_total[1m])) by (receiver, transport)) or vector(0)\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"{{receiver}}-{{transport}}\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"% Spans Refused\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"percentunit\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": 1,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            }\n         ],\n         \"repeat\": null,\n         \"repeatIteration\": null,\n         \"repeatRowId\": null,\n         \"showTitle\": true,\n         \"title\": \"Collector - Ingestion\",\n         \"titleSize\": \"h6\"\n      },\n      {\n         \"collapse\": false,\n         \"height\": \"250px\",\n         \"panels\": [\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 3,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"sum(rate(otelcol_exporter_send_failed_spans_total[1m])) or vector(0)\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"error\",\n                     \"legendLink\": null\n                  },\n                  {\n                     \"expr\": \"sum(rate(otelcol_exporter_sent_spans_total[1m]))\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"success\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Span Export Rate\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            },\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 4,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"(sum(rate(otelcol_exporter_sent_spans_total[1m])) by (exporter) / (sum(rate(otelcol_exporter_sent_spans_total[1m])) by (exporter) + sum(rate(otelcol_exporter_send_failed_spans_total[1m])) by (exporter))) * 100 or vector(0)\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"{{exporter}}\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Export Success Rate %\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"percent\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": 100,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            }\n         ],\n         \"repeat\": null,\n         \"repeatIteration\": null,\n         \"repeatRowId\": null,\n         \"showTitle\": true,\n         \"title\": \"Collector - Export\",\n         \"titleSize\": \"h6\"\n      },\n      {\n         \"collapse\": false,\n         \"height\": \"250px\",\n         \"panels\": [\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 5,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"sum(rate(jaeger_storage_requests_total[1m])) by (operation, result)\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"{{operation}} - {{result}}\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Storage Request Rate\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            },\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 6,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"histogram_quantile(0.99, sum(rate(jaeger_storage_latency_seconds_bucket[1m])) by (le, operation))\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"{{operation}}\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Storage Latency - P99\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"s\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            }\n         ],\n         \"repeat\": null,\n         \"repeatIteration\": null,\n         \"repeatRowId\": null,\n         \"showTitle\": true,\n         \"title\": \"Storage\",\n         \"titleSize\": \"h6\"\n      },\n      {\n         \"collapse\": false,\n         \"height\": \"250px\",\n         \"panels\": [\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 7,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"sum(rate(http_server_request_duration_seconds_count{http_route=\\\"/api/traces\\\"}[1m])) by (http_response_status_code)\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"status {{http_response_status_code}}\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Query Request Rate\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            },\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 8,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"histogram_quantile(0.99, sum(rate(http_server_request_duration_seconds_bucket{http_route=\\\"/api/traces\\\"}[1m])) by (le))\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"P99\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Query Latency - P99\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"s\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            }\n         ],\n         \"repeat\": null,\n         \"repeatIteration\": null,\n         \"repeatRowId\": null,\n         \"showTitle\": true,\n         \"title\": \"Query\",\n         \"titleSize\": \"h6\"\n      },\n      {\n         \"collapse\": false,\n         \"height\": \"250px\",\n         \"panels\": [\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 9,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"rate(otelcol_process_cpu_seconds_total[1m])\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"CPU\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"CPU Usage\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"percentunit\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            },\n            {\n               \"aliasColors\": { },\n               \"bars\": false,\n               \"dashLength\": 10,\n               \"dashes\": false,\n               \"datasource\": \"$datasource\",\n               \"fill\": 10,\n               \"id\": 10,\n               \"legend\": {\n                  \"avg\": false,\n                  \"current\": false,\n                  \"max\": false,\n                  \"min\": false,\n                  \"show\": true,\n                  \"total\": false,\n                  \"values\": false\n               },\n               \"lines\": true,\n               \"linewidth\": 0,\n               \"links\": [ ],\n               \"nullPointMode\": \"null as zero\",\n               \"percentage\": false,\n               \"pointradius\": 5,\n               \"points\": false,\n               \"renderer\": \"flot\",\n               \"seriesOverrides\": [ ],\n               \"spaceLength\": 10,\n               \"span\": 6,\n               \"stack\": true,\n               \"steppedLine\": false,\n               \"targets\": [\n                  {\n                     \"expr\": \"otelcol_process_memory_rss_bytes\",\n                     \"format\": \"time_series\",\n                     \"legendFormat\": \"Memory\",\n                     \"legendLink\": null\n                  }\n               ],\n               \"thresholds\": [ ],\n               \"timeFrom\": null,\n               \"timeShift\": null,\n               \"title\": \"Memory RSS\",\n               \"tooltip\": {\n                  \"shared\": true,\n                  \"sort\": 2,\n                  \"value_type\": \"individual\"\n               },\n               \"type\": \"graph\",\n               \"xaxis\": {\n                  \"buckets\": null,\n                  \"mode\": \"time\",\n                  \"name\": null,\n                  \"show\": true,\n                  \"values\": [ ]\n               },\n               \"yaxes\": [\n                  {\n                     \"format\": \"bytes\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": 0,\n                     \"show\": true\n                  },\n                  {\n                     \"format\": \"short\",\n                     \"label\": null,\n                     \"logBase\": 1,\n                     \"max\": null,\n                     \"min\": null,\n                     \"show\": false\n                  }\n               ]\n            }\n         ],\n         \"repeat\": null,\n         \"repeatIteration\": null,\n         \"repeatRowId\": null,\n         \"showTitle\": true,\n         \"title\": \"System\",\n         \"titleSize\": \"h6\"\n      }\n   ],\n   \"schemaVersion\": 14,\n   \"style\": \"dark\",\n   \"tags\": [ ],\n   \"templating\": {\n      \"list\": [\n         {\n            \"current\": {\n               \"text\": \"default\",\n               \"value\": \"default\"\n            },\n            \"hide\": 0,\n            \"label\": \"Data source\",\n            \"name\": \"datasource\",\n            \"options\": [ ],\n            \"query\": \"prometheus\",\n            \"refresh\": 1,\n            \"regex\": \"\",\n            \"type\": \"datasource\"\n         }\n      ]\n   },\n   \"time\": {\n      \"from\": \"now-1h\",\n      \"to\": \"now\"\n   },\n   \"timepicker\": {\n      \"refresh_intervals\": [\n         \"5s\",\n         \"10s\",\n         \"30s\",\n         \"1m\",\n         \"5m\",\n         \"15m\",\n         \"30m\",\n         \"1h\",\n         \"2h\",\n         \"1d\"\n      ],\n      \"time_options\": [\n         \"5m\",\n         \"15m\",\n         \"1h\",\n         \"6h\",\n         \"12h\",\n         \"24h\",\n         \"2d\",\n         \"7d\",\n         \"30d\"\n      ]\n   },\n   \"timezone\": \"utc\",\n   \"title\": \"Jaeger v2\",\n   \"uid\": \"\",\n   \"version\": 0\n}\n"
  },
  {
    "path": "monitoring/jaeger-mixin/dashboards.libsonnet",
    "content": "local g = (import 'grafana-builder/grafana.libsonnet');\n\n{\n  grafanaDashboards+: {\n    'jaeger.json':\n      g.dashboard('Jaeger v2')\n      .addRow(\n        g.row('Collector - Ingestion')\n        .addPanel(\n          g.panel('Span Ingest Rate') +\n          g.queryPanel(\n            [\n              'sum(rate(otelcol_receiver_refused_spans_total[1m])) or vector(0)',\n              'sum(rate(otelcol_receiver_accepted_spans_total[1m]))',\n            ],\n            [\n              'error',\n              'success',\n            ]\n          ) +\n          g.stack\n        )\n        .addPanel(\n          g.panel('% Spans Refused') +\n          g.queryPanel(\n            'sum(rate(otelcol_receiver_refused_spans_total[1m])) by (receiver, transport) / (sum(rate(otelcol_receiver_accepted_spans_total[1m])) by (receiver, transport) + sum(rate(otelcol_receiver_refused_spans_total[1m])) by (receiver, transport)) or vector(0)',\n            '{{receiver}}-{{transport}}'\n          ) +\n          { yaxes: g.yaxes({ format: 'percentunit', max: 1 }) } +\n          g.stack\n        )\n      )\n      .addRow(\n        g.row('Collector - Export')\n        .addPanel(\n          g.panel('Span Export Rate') +\n          g.queryPanel(\n            [\n              'sum(rate(otelcol_exporter_send_failed_spans_total[1m])) or vector(0)',\n              'sum(rate(otelcol_exporter_sent_spans_total[1m]))',\n            ],\n            [\n              'error',\n              'success',\n            ]\n          ) +\n          g.stack\n        )\n        .addPanel(\n          g.panel('Export Success Rate %') +\n          g.queryPanel(\n            '(sum(rate(otelcol_exporter_sent_spans_total[1m])) by (exporter) / (sum(rate(otelcol_exporter_sent_spans_total[1m])) by (exporter) + sum(rate(otelcol_exporter_send_failed_spans_total[1m])) by (exporter))) * 100 or vector(0)',\n            '{{exporter}}'\n          ) +\n          { yaxes: g.yaxes({ format: 'percent', max: 100 }) } +\n          g.stack\n        )\n      )\n      .addRow(\n        g.row('Storage')\n        .addPanel(\n          g.panel('Storage Request Rate') +\n          g.queryPanel(\n            'sum(rate(jaeger_storage_requests_total[1m])) by (operation, result)',\n            '{{operation}} - {{result}}'\n          ) +\n          g.stack\n        )\n        .addPanel(\n          g.panel('Storage Latency - P99') +\n          g.queryPanel(\n            'histogram_quantile(0.99, sum(rate(jaeger_storage_latency_seconds_bucket[1m])) by (le, operation))',\n            '{{operation}}'\n          ) +\n          { yaxes: g.yaxes({ format: 's' }) } +\n          g.stack\n        )\n      )\n      .addRow(\n        g.row('Query')\n        .addPanel(\n          g.panel('Query Request Rate') +\n          g.queryPanel(\n            'sum(rate(http_server_request_duration_seconds_count{http_route=\"/api/traces\"}[1m])) by (http_response_status_code)',\n            'status {{http_response_status_code}}'\n          ) +\n          g.stack\n        )\n        .addPanel(\n          g.panel('Query Latency - P99') +\n          g.queryPanel(\n            'histogram_quantile(0.99, sum(rate(http_server_request_duration_seconds_bucket{http_route=\"/api/traces\"}[1m])) by (le))',\n            'P99'\n          ) +\n          { yaxes: g.yaxes({ format: 's' }) } +\n          g.stack\n        )\n      )\n      .addRow(\n        g.row('System')\n        .addPanel(\n          g.panel('CPU Usage') +\n          g.queryPanel(\n            'rate(otelcol_process_cpu_seconds_total[1m])',\n            'CPU'\n          ) +\n          { yaxes: g.yaxes({ format: 'percentunit' }) } +\n          g.stack\n        )\n        .addPanel(\n          g.panel('Memory RSS') +\n          g.queryPanel(\n            'otelcol_process_memory_rss_bytes',\n            'Memory'\n          ) +\n          { yaxes: g.yaxes({ format: 'bytes' }) } +\n          g.stack\n        )\n      ),\n  },\n}"
  },
  {
    "path": "monitoring/jaeger-mixin/jsonnetfile.json",
    "content": "{\n    \"dependencies\": [\n        {\n            \"name\": \"grafana-builder\",\n            \"source\": {\n                \"git\": {\n                    \"remote\": \"https://github.com/grafana/jsonnet-libs\",\n                    \"subdir\": \"grafana-builder\"\n                }\n            },\n            \"version\": \"master\"\n        }\n    ]\n}\n"
  },
  {
    "path": "monitoring/jaeger-mixin/mixin.libsonnet",
    "content": "(import 'dashboards.libsonnet') +\n(import 'alerts.libsonnet')\n"
  },
  {
    "path": "monitoring/jaeger-mixin/monitoring-setup.example.jsonnet",
    "content": "local jaegerAlerts = (import 'jaeger-mixin/alerts.libsonnet').prometheusAlerts;\nlocal jaegerDashboard = (import 'jaeger-mixin/mixin.libsonnet').grafanaDashboards;\n\nlocal kp =\n  (import 'kube-prometheus/main.libsonnet') +\n  {\n    values+:: {\n      common+: {\n        namespace: 'observability',\n      },\n      grafana+: {\n        dashboards+:: {\n          'my-dashboard.json': jaegerDashboard['jaeger.json'],\n        },\n      },\n    },\n    exampleApplication: {\n      prometheusRuleExample: {\n        apiVersion: 'monitoring.coreos.com/v1',\n        kind: 'PrometheusRule',\n        metadata: {\n          name: 'my-prometheus-rule',\n          namespace: $.values.common.namespace,\n        },\n        spec: jaegerAlerts,\n      },\n    },\n  };\n{ ['00namespace-' + name + '.json']: kp.kubePrometheus[name] for name in std.objectFields(kp.kubePrometheus) } +\n{ ['0prometheus-operator-' + name + '.json']: kp.prometheusOperator[name] for name in std.objectFields(kp.prometheusOperator) } +\n{ ['node-exporter-' + name + '.json']: kp.nodeExporter[name] for name in std.objectFields(kp.nodeExporter) } +\n{ ['kube-state-metrics-' + name + '.json']: kp.kubeStateMetrics[name] for name in std.objectFields(kp.kubeStateMetrics) } +\n{ ['alertmanager-' + name + '.json']: kp.alertmanager[name] for name in std.objectFields(kp.alertmanager) } +\n{ ['prometheus-' + name + '.json']: kp.prometheus[name] for name in std.objectFields(kp.prometheus) } +\n{ ['prometheus-adapter-' + name + '.json']: kp.prometheusAdapter[name] for name in std.objectFields(kp.prometheusAdapter) } +\n{ ['grafana-' + name + '.json']: kp.grafana[name] for name in std.objectFields(kp.grafana) } +\n{ ['my-application-' + name + '.json']: kp.exampleApplication[name] for name in std.objectFields(kp.exampleApplication) }\n"
  },
  {
    "path": "monitoring/jaeger-mixin/prometheus_alerts.yml",
    "content": "\"groups\":\n- \"name\": \"jaeger_alerts\"\n  \"rules\":\n  - \"alert\": \"JaegerHTTPServerErrs\"\n    \"annotations\":\n      \"message\": |\n        {{ $labels.job }} {{ $labels.instance }} is experiencing {{ printf \"%.2f\" $value }}% HTTP errors.\n    \"expr\": \"100 * sum(rate(jaeger_agent_http_server_errors_total[1m])) by (instance, job, namespace) / sum(rate(jaeger_agent_http_server_total[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerRPCRequestsErrors\"\n    \"annotations\":\n      \"message\": |\n        {{ $labels.job }} {{ $labels.instance }} is experiencing {{ printf \"%.2f\" $value }}% RPC HTTP errors.\n    \"expr\": \"100 * sum(rate(jaeger_client_jaeger_rpc_http_requests{status_code=~\\\"4xx|5xx\\\"}[1m])) by (instance, job, namespace) / sum(rate(jaeger_client_jaeger_rpc_http_requests[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerClientSpansDropped\"\n    \"annotations\":\n      \"message\": |\n        service {{ $labels.job }} {{ $labels.instance }} is dropping {{ printf \"%.2f\" $value }}% spans.\n    \"expr\": \"100 * sum(rate(jaeger_reporter_spans{result=~\\\"dropped|err\\\"}[1m])) by (instance, job, namespace) / sum(rate(jaeger_reporter_spans[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerAgentSpansDropped\"\n    \"annotations\":\n      \"message\": |\n        agent {{ $labels.job }} {{ $labels.instance }} is dropping {{ printf \"%.2f\" $value }}% spans.\n    \"expr\": \"100 * sum(rate(jaeger_agent_reporter_batches_failures_total[1m])) by (instance, job, namespace) / sum(rate(jaeger_agent_reporter_batches_submitted_total[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerCollectorDroppingSpans\"\n    \"annotations\":\n      \"message\": |\n        collector {{ $labels.job }} {{ $labels.instance }} is dropping {{ printf \"%.2f\" $value }}% spans.\n    \"expr\": \"100 * sum(rate(jaeger_collector_spans_dropped_total[1m])) by (instance, job, namespace) / sum(rate(jaeger_collector_spans_received_total[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerSamplingUpdateFailing\"\n    \"annotations\":\n      \"message\": |\n        {{ $labels.job }} {{ $labels.instance }} is failing {{ printf \"%.2f\" $value }}% in updating sampling policies.\n    \"expr\": \"100 * sum(rate(jaeger_sampler_queries{result=\\\"err\\\"}[1m])) by (instance, job, namespace) / sum(rate(jaeger_sampler_queries[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerThrottlingUpdateFailing\"\n    \"annotations\":\n      \"message\": |\n        {{ $labels.job }} {{ $labels.instance }} is failing {{ printf \"%.2f\" $value }}% in updating throttling policies.\n    \"expr\": \"100 * sum(rate(jaeger_throttler_updates{result=\\\"err\\\"}[1m])) by (instance, job, namespace) / sum(rate(jaeger_throttler_updates[1m])) by (instance, job, namespace)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n  - \"alert\": \"JaegerQueryReqsFailing\"\n    \"annotations\":\n      \"message\": |\n        {{ $labels.job }} {{ $labels.instance }} is seeing {{ printf \"%.2f\" $value }}% query errors on {{ $labels.operation }}.\n    \"expr\": \"100 * sum(rate(jaeger_query_requests_total{result=\\\"err\\\"}[1m])) by (instance, job, namespace, operation) / sum(rate(jaeger_query_requests_total[1m])) by (instance, job, namespace, operation)> 1\"\n    \"for\": \"15m\"\n    \"labels\":\n      \"severity\": \"warning\"\n"
  },
  {
    "path": "monitoring/jaeger-mixin/prometheus_alerts_v2.yml",
    "content": "groups:\n- name: jaeger_alerts\n  rules:\n  - alert: OtelHttpServerErrors\n    annotations:\n      message: |\n        {{ $labels.job }} {{ $labels.instance }} is experiencing {{ printf \"%.2f\" $value }}% HTTP errors.\n    expr: |\n      100 * sum(rate(otelcol_http_server_duration_count{http_status_code=~\"5..\"}[1m])) by (instance, job) / \n      sum(rate(otelcol_http_server_duration_count[1m])) by (instance, job) > 1\n    for: 15m\n    labels:\n      severity: warning\n\n  - alert: OtelExporterQueueFull\n    annotations:\n      message: |\n        {{ $labels.job }} {{ $labels.instance }} exporter queue is at {{ printf \"%.2f\" $value }} items (over 80% capacity).\n    expr: |\n      100 * otelcol_exporter_queue_size / otelcol_exporter_queue_capacity > 80\n    for: 15m\n    labels:\n      severity: warning\n\n  - alert: OtelHighMemoryUsage\n    annotations:\n      message: |\n        {{ $labels.job }} {{ $labels.instance }} memory usage is high at {{ humanize $value }} bytes.\n    expr: |\n      otelcol_process_memory_rss > 100000000\n    for: 15m\n    labels:\n      severity: warning\n\n  - alert: OtelHighCpuUsage\n    annotations:\n      message: |\n        {{ $labels.job }} {{ $labels.instance }} CPU usage is high ({{ printf \"%.2f\" $value }} seconds of CPU time in 5m).\n    expr: |\n      rate(otelcol_process_cpu_seconds[5m]) > 0.8\n    for: 15m\n    labels:\n      severity: warning\n\n  - alert: OtelProcessorBatchHighCardinality\n    annotations:\n      message: |\n        {{ $labels.job }} {{ $labels.instance }} has high metadata cardinality ({{ printf \"%.0f\" $value }} combinations).\n    expr: |\n      otelcol_processor_batch_metadata_cardinality > 1000\n    for: 15m\n    labels:\n      severity: warning"
  },
  {
    "path": "ports/ports.go",
    "content": "// Copyright (c) 2019 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage ports\n\nimport (\n\t\"strconv\"\n)\n\nconst (\n\t// CollectorV2GRPC is the HTTP port for remote sampling extension\n\tCollectorV2SamplingHTTP = 5778\n\t// CollectorV2GRPC is the gRPC port for remote sampling extension\n\tCollectorV2SamplingGRPC = 5779\n\t// CollectorV2HealthChecks is the port for health checks extension\n\tCollectorV2HealthChecks = 13133\n\n\t// QueryGRPC is the default port of GRPC requests for Query trace retrieval\n\tQueryGRPC = 16685\n\t// QueryHTTP is the default port for UI and Query API (e.g. /api/* endpoints)\n\tQueryHTTP = 16686\n\t// MCPHTTP is the default port for MCP (Model Context Protocol) server HTTP endpoint\n\tMCPHTTP = 16687\n\n\t// RemoteStorageGRPC is the default port of GRPC requests for Remote Storage\n\tRemoteStorageGRPC = 17271\n\t// RemoteStorageHTTP is the default admin HTTP port (health check, metrics, etc.)\n\tRemoteStorageAdminHTTP = 17270\n)\n\n// PortToHostPort converts the port into a host:port address string\nfunc PortToHostPort(port int) string {\n\treturn \":\" + strconv.Itoa(port)\n}\n"
  },
  {
    "path": "ports/ports_test.go",
    "content": "// Copyright (c) 2020 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\npackage ports\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/jaegertracing/jaeger/internal/testutils\"\n)\n\nfunc TestPortToHostPort(t *testing.T) {\n\tassert.Equal(t, \":42\", PortToHostPort(42))\n}\n\nfunc TestMain(m *testing.M) {\n\ttestutils.VerifyGoLeaks(m)\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:best-practices\",\n    \":gitSignOff\"\n  ],\n  \"dependencyDashboard\": true,\n  \"dependencyDashboardHeader\": \"Project settings are at https://developer.mend.io/\",\n  \"labels\": [\n    \"changelog:dependencies\"\n  ],\n  \"postUpdateOptions\": [\n    \"gomodTidy\",\n    \"gomodUpdateImportPaths\"\n  ],\n  \"suppressNotifications\": [\n    \"prEditedNotification\"\n  ],\n  \"schedule\": [\n    \"on the first day of the month\"\n  ],\n  \"packageRules\": [\n    {\n      \"matchFileNames\": [\n        \"docker-compose/**/docker-compose.y*ml\"\n      ],\n      \"matchUpdateTypes\": [\n        \"major\",\n        \"patch\",\n        \"digest\"\n      ],\n      \"enabled\": false\n    },\n    {\n      \"matchManagers\": [\n        \"github-actions\"\n      ],\n      \"groupName\": \"github-actions deps\"\n    },\n    {\n      \"matchManagers\": [\n        \"github-actions\"\n      ],\n      \"matchUpdateTypes\": [\n        \"patch\",\n        \"digest\"\n      ],\n      \"enabled\": false\n    },\n    {\n      \"groupName\": \"All OTEL SDK + contrib packages\",\n      \"groupSlug\": \"go-otel-sdk\",\n      \"matchDatasources\": [\n        \"go\"\n      ],\n      \"matchPackageNames\": [\n        \"go.opentelemetry.io/otel/**\",\n        \"go.opentelemetry.io/contrib/**\",\n        \"github.com/open-telemetry/opentelemetry-go-contrib/**\"\n      ],\n      \"schedule\": [\n        \"on friday\"\n      ]\n    },\n    {\n      \"groupName\": \"All OTEL Collector packages\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchPackageNames\": [\n        \"go.opentelemetry.io/collector{/,}**\"\n      ],\n      \"schedule\": [\n        \"on friday\"\n      ]\n    },\n    {\n      \"groupName\": \"All OTEL Collector contrib packages\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchPackageNames\": [\n        \"github.com/open-telemetry/opentelemetry-collector-contrib{/,}**\"\n      ],\n      \"schedule\": [\n        \"on friday\"\n      ]\n    },\n    {\n      \"groupName\": \"All google.golang.org packages\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchSourceUrls\": [\n        \"google.golang.org{/,}**\"\n      ]\n    },\n    {\n      \"groupName\": \"All golang.org/x packages\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchPackageNames\": [\n        \"golang.org/x{/,}**\"\n      ]\n    },\n    {\n      \"groupName\": \"All github.com/prometheus packages\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchPackageNames\": [\n        \"github.com/prometheus{/,}**\"\n      ]\n    },\n    {\n      \"groupName\": \"Exclude frequent tools upgrades\",\n      \"matchDatasources\": [\n        \"go\"\n      ],\n      \"matchPackageNames\": [\n        \"github.com/vektra/mockery/**\"\n      ],\n      \"matchUpdateTypes\": [\n        \"patch\"\n      ],\n      \"enabled\": false\n    },\n    {\n      \"groupName\": \"All Jaeger Docker images\",\n      \"matchDatasources\": [\n        \"docker\"\n      ],\n      \"matchPackageNames\": [\n        \"cr.jaegertracing.io/jaegertracing/jaeger\",\n        \"cr.jaegertracing.io/jaegertracing/jaeger-tracegen\"\n      ]\n    },\n    {\n      \"groupName\": \"All ClickHouse packages\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchPackageNames\": [\n        \"github.com/ClickHouse/ch-go\",\n        \"github.com/ClickHouse/clickhouse-go/v2\"\n      ]\n    },\n    {\n      \"groupName\": \"OpenSearch 3.x docker images\",\n      \"matchDatasources\": [\n        \"docker\"\n      ],\n      \"matchPackageNames\": [\n        \"opensearchproject/opensearch\"\n      ],\n      \"matchFileNames\": [\n        \"docker-compose/opensearch/v3/docker-compose.yml\",\n        \"docker-compose/monitor/docker-compose-opensearch.yml\"\n      ],\n      \"allowedVersions\": \"3.x\"\n    },\n    {\n      \"groupName\": \"Elasticsearch 9.x docker images\",\n      \"matchDatasources\": [\n        \"docker\"\n      ],\n      \"matchPackageNames\": [\n        \"docker.elastic.co/elasticsearch/elasticsearch\"\n      ],\n      \"matchFileNames\": [\n        \"docker-compose/elasticsearch/v9/docker-compose.yml\",\n        \"docker-compose/monitor/docker-compose-elasticsearch.yml\"\n      ],\n      \"allowedVersions\": \"9.x\"\n    }\n  ]\n}\n"
  },
  {
    "path": "scripts/build/build-all-in-one-image.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\nprint_help() {\n  echo \"Usage: $0 [-D] [-h] [-l] [-o] [-p platforms]\"\n  echo \"  -D: Disable building of images with debugger\"\n  echo \"  -h: Print help\"\n  echo \"  -l: Enable local-only mode that only pushes images to local registry\"\n  echo \"  -o: overwrite image in the target remote repository even if the semver tag already exists\"\n  echo \"  -p: Comma-separated list of platforms to build for (default: all supported)\"\n  exit 1\n}\n\nadd_debugger='Y'\nplatforms=\"$(make echo-linux-platforms)\"\nFLAGS=()\nBINARY=\"jaeger\"\n\n# this script doesn't use BRANCH and GITHUB_SHA itself, but its dependency scripts do.\nexport BRANCH=${BRANCH?'env var is required'}\nexport GITHUB_SHA=${GITHUB_SHA:-$(git rev-parse HEAD)}\n\nwhile getopts \"Dhlop:\" opt; do\n\tcase \"${opt}\" in\n\tD)\n\t\tadd_debugger='N'\n\t\t;;\n\tl)\n\t\t# in the local-only mode the images will only be pushed to local registry\n\t\tFLAGS=(\"${FLAGS[@]}\" -l)\n\t\t;;\n\to)\n\t\tFLAGS=(\"${FLAGS[@]}\" -o)\n\t\t;;\n\tp)\n\t\tplatforms=${OPTARG}\n\t\t;;\n\t?)\n\t\tprint_help\n\t\t;;\n\tesac\ndone\n\n# remove flags, leave only positional args\nshift $((OPTIND - 1))\n\n# Only build the jaeger binary\nexport HEALTHCHECK_V2=true\n\nset -x\n\n# Set default GOARCH variable to the host GOARCH, the target architecture can\n# be overrided by passing architecture value to the script:\n# `GOARCH=<target arch> ./scripts/build/build-all-in-one-image.sh`.\nGOARCH=${GOARCH:-$(go env GOARCH)}\nimage=\"jaegertracing/${BINARY}\"\n\nmake build-ui\n\nrun_integration_test() {\n  local image_name=\"$1\"\n  CID=$(docker run -d -p 16686:16686 -p 13133:13133 -p 5778:5778 \"${image_name}:${GITHUB_SHA}\")\n\n  if ! make all-in-one-integration-test ; then\n      echo \"---- integration test failed unexpectedly ----\"\n      echo \"--- check the docker log below for details ---\"\n      echo \"::group::docker logs\"\n        docker logs \"$CID\"\n      echo \"::endgroup::\"\n      docker kill \"$CID\"\n      exit 1\n  fi\n  docker kill \"$CID\"\n}\n\nbuild_test_upload() {\n  # Loop through each platform (separated by commas)\n  for platform in $(echo \"$platforms\" | tr ',' ' '); do\n    arch=${platform##*/}  # Remove everything before the last slash\n    make \"build-${BINARY}\" GOOS=linux GOARCH=\"${arch}\"\n  done\n\n  make create-baseimg LINUX_PLATFORMS=\"$platforms\"\n\n  # build all-in-one image locally for integration test (the explicit -l switch)\n  bash scripts/build/build-upload-a-docker-image.sh -l -b -c \"${BINARY}\" -d \"cmd/${BINARY}\" -p \"${platforms}\" -t release\n\n  run_integration_test \"localhost:5000/$image\"\n\n  # build all-in-one image and upload to dockerhub/quay.io\n  bash scripts/build/build-upload-a-docker-image.sh \"${FLAGS[@]}\" -b -c \"${BINARY}\" -d \"cmd/${BINARY}\" -p \"${platforms}\" -t release\n}\n\nbuild_test_upload_with_debugger() {\n  make \"build-${BINARY}\" GOOS=linux GOARCH=\"$GOARCH\" DEBUG_BINARY=1\n\n  make create-baseimg-debugimg LINUX_PLATFORMS=\"$platforms\"\n\n  # build locally for integration test (the -l switch)\n  bash scripts/build/build-upload-a-docker-image.sh -l -b -c \"${BINARY}-debug\" -d \"cmd/${BINARY}\" -p \"${platforms}\" -t release -t debug\n  run_integration_test \"localhost:5000/${image}-debug\"\n\n  # build & upload official image\n  bash scripts/build/build-upload-a-docker-image.sh \"${FLAGS[@]}\" -b -c \"${BINARY}-debug\" -d \"cmd/${BINARY}\" -p \"${platforms}\" -t debug\n}\n\nbuild_test_upload\n\nif [[ \"${add_debugger}\" == \"Y\" ]]; then\n  build_test_upload_with_debugger\nfi\n"
  },
  {
    "path": "scripts/build/build-hotrod-image.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -exuf -o pipefail\n\nprint_help() {\n  echo \"Usage: $0 [-h] [-l] [-o] [-p platforms] [-r runtime]\"\n  echo \"-h: Print help\"\n  echo \"-l: Enable local-only mode that only pushes images to local registry\"\n  echo \"-o: overwrite image in the target remote repository even if the semver tag already exists\"\n  echo \"-p: Comma-separated list of platforms to build for (default: all supported)\"\n  echo \"-r: Runtime to test with (docker|k8s, default: docker)\"\n  exit 1\n}\n\ndocker_compose_file=\"./examples/hotrod/docker-compose.yml\"\nplatforms=\"$(make echo-linux-platforms)\"\ncurrent_platform=\"$(go env GOOS)/$(go env GOARCH)\"\nbinary=\"jaeger\"\nFLAGS=()\nsuccess=\"false\"\nruntime=\"docker\"\n\nwhile getopts \"hlop:r:\" opt; do\n\tcase \"${opt}\" in\n\tl)\n\t\t# in the local-only mode the images will only be pushed to local registry\n    FLAGS=(\"${FLAGS[@]}\" -l)\n\t\t;;\n\to)\n\t\tFLAGS=(\"${FLAGS[@]}\" -o)\n\t\t;;\n\tp)\n\t\tplatforms=${OPTARG}\n\t\t;;\n  r)\n\t\tcase \"${OPTARG}\" in\n\t\t\tdocker|k8s) runtime=\"${OPTARG}\" ;;\n\t\t\t*) echo \"Invalid runtime: ${OPTARG}. Use 'docker' or 'k8s'\" >&2; exit 1 ;;\n\t\tesac\n\t\t;;\n\t*)\n\t\tprint_help\n\t\t;;\n\tesac\ndone\n\nset -x\n\ndump_logs() {\n  local runtime=$1\n  local compose_file=$2\n\n  echo \"::group:: Logs\"\n  if [ \"$runtime\" == \"k8s\" ]; then\n    kubectl logs -l app.kubernetes.io/name=jaeger\n  else\n    docker compose -f \"$compose_file\" logs\n  fi\n  echo \"::endgroup::\"\n}\n\nteardown() {\n  echo \"::group::Tearing down...\"\n  if [[ \"$success\" == \"false\" ]]; then\n      dump_logs \"${runtime}\" \"${docker_compose_file}\"\n  fi\n  if [[ \"${runtime}\" == \"k8s\" ]]; then\n    if [[ -n \"${HOTROD_PORT_FWD_PID:-}\" ]]; then\n      kill \"$HOTROD_PORT_FWD_PID\" || true\n    fi\n    if [[ -n \"${JAEGER_PORT_FWD_PID:-}\" ]]; then\n      kill \"$JAEGER_PORT_FWD_PID\" || true\n    fi\n    helm uninstall jaeger --ignore-not-found || true\n    helm uninstall prometheus --ignore-not-found || true\n  else\n    docker compose -f \"$docker_compose_file\" down\n  fi\n  \n  echo \"::endgroup::\"\n}\ntrap teardown EXIT\n\nmake prepare-docker-buildx\nmake create-baseimg LINUX_PLATFORMS=\"$platforms\"\n\n# Build hotrod binary for each target platform (separated by commas)\nfor platform in $(echo \"$platforms\" | tr ',' ' '); do\n  # Extract the operating system from the platform string\n  os=${platform%%/*}  #remove everything after the last slash\n  # Extract the architecture from the platform string\n  arch=${platform##*/}  # Remove everything before the last slash\n  make build-examples GOOS=\"${os}\" GOARCH=\"${arch}\"\ndone\n\n# Build hotrod image locally (-l) for integration test.\n# Note: hotrod's Dockerfile is different from main binaries,\n# so we do not pass flags like -b and -t.\nbash scripts/build/build-upload-a-docker-image.sh -l -c example-hotrod -d examples/hotrod -p \"${current_platform}\"\n\n# Build jaeger image locally (-l) for integration test\nmake build-${binary}\nbash scripts/build/build-upload-a-docker-image.sh -l -b -c \"${binary}\" -d cmd/\"${binary}\" -p \"${current_platform}\" -t release\n\nif [[ \"${runtime}\" == \"k8s\" ]]; then\n  if ! kubectl cluster-info >/dev/null 2>&1; then\n    echo \"Error: Cannot connect to Kubernetes cluster\"\n    exit 1\n  fi\n\n  echo '::group:: run on Kubernetes'\n  echo '::group:: Loading images into Kind cluster'\n  \n  docker pull localhost:5000/jaegertracing/jaeger:\"${GITHUB_SHA}\"\n  docker pull localhost:5000/jaegertracing/example-hotrod:\"${GITHUB_SHA}\"\n  \n  # Get the actual cluster name\n  CLUSTER_NAME=$(kind get clusters | head -n1)\n  if [[ -n \"$CLUSTER_NAME\" ]]; then\n    echo \"Loading images into '$CLUSTER_NAME' cluster...\"\n    kind load docker-image localhost:5000/jaegertracing/jaeger:\"${GITHUB_SHA}\" --name \"$CLUSTER_NAME\"\n    kind load docker-image localhost:5000/jaegertracing/example-hotrod:\"${GITHUB_SHA}\" --name \"$CLUSTER_NAME\"\n  else\n    echo \"No Kind clusters found!\"\n    exit 1\n  fi\n  \n  bash ./examples/oci/deploy-all.sh local \"${GITHUB_SHA}\"\n  kubectl wait --for=condition=available --timeout=180s deployment/jaeger-hotrod\n  kubectl wait --for=condition=available --timeout=180s deployment/jaeger\n\n  kubectl port-forward svc/jaeger-hotrod 8080:80 &\n  HOTROD_PORT_FWD_PID=$!\n  kubectl port-forward svc/jaeger-query 16686:16686 &\n  JAEGER_PORT_FWD_PID=$!\n  echo '::endgroup::'\n\nelse\n  echo '::group:: docker compose'\n  JAEGER_VERSION=$GITHUB_SHA HOTROD_VERSION=$GITHUB_SHA REGISTRY=\"localhost:5000/\" docker compose -f \"$docker_compose_file\" up -d\n  echo '::endgroup::'\nfi\n\nif [[ \"${runtime}\" == \"k8s\" ]]; then\n  HOTROD_URL=\"http://localhost:8080/hotrod\"\n  JAEGER_QUERY_URL=\"http://localhost:16686/jaeger\"\nelse\n  HOTROD_URL=\"http://localhost:8080\"\n  JAEGER_QUERY_URL=\"http://localhost:16686\"\nfi\n\ni=0\nwhile [[ \"$(curl -s -o /dev/null -w '%{http_code}' ${HOTROD_URL})\" != \"200\" && $i -lt 30 ]]; do\n  sleep 1\n  i=$((i+1))\ndone\n\necho '::group:: check HTML'\necho 'Check that home page contains text Rides On Demand'\nbody=$(curl ${HOTROD_URL})\nif [[ $body != *\"Rides On Demand\"* ]]; then\n  echo \"String \\\"Rides On Demand\\\" is not present on the index page\"\n  exit 1\nfi\necho '::endgroup::'\n\nresponse=$(curl -i -X POST \"${HOTROD_URL}/dispatch?customer=123\")\nTRACE_ID=$(echo \"$response\" | grep -Fi \"Traceresponse:\" | awk '{print $2}' | cut -d '-' -f 2)\n\nif [ -n \"$TRACE_ID\" ]; then\n  echo \"TRACE_ID is not empty: $TRACE_ID\"\nelse\n  echo \"TRACE_ID is empty\"\n  exit 1\nfi\n\nEXPECTED_SPANS=35\nMAX_RETRIES=30\nSLEEP_INTERVAL=3\n\npoll_jaeger() {\n  local trace_id=$1\n  local url=\"${JAEGER_QUERY_URL}/api/traces/${trace_id}\"\n\n  curl -s \"${url}\" | jq '.data[0].spans | length' || echo \"0\"\n}\n\n# Poll Jaeger until trace with desired number of spans is loaded or we timeout.\nspan_count=0\nfor ((i=1; i<=MAX_RETRIES; i++)); do\n  span_count=$(poll_jaeger \"${TRACE_ID}\")\n\n  if [[ \"$span_count\" -ge \"$EXPECTED_SPANS\" ]]; then\n    echo \"Trace found with $span_count spans.\"\n    break\n  fi\n\n  echo \"Retry $i/$MAX_RETRIES: Trace not found or insufficient spans ($span_count/$EXPECTED_SPANS). Retrying in $SLEEP_INTERVAL seconds...\"\n  sleep $SLEEP_INTERVAL\ndone\n\nif [[ \"$span_count\" -lt \"$EXPECTED_SPANS\" ]]; then\n  echo \"Failed to find the trace with the expected number of spans within the timeout period.\"\n  exit 1\nfi\n\nsuccess=\"true\"\n\n# Ensure the image is published after successful test (maybe with -l flag if on a pull request).\n# This is where all those multi-platform binaries we built earlier are utilized.\nbash scripts/build/build-upload-a-docker-image.sh \"${FLAGS[@]}\" -c example-hotrod -d examples/hotrod -p \"${platforms}\"\n\n"
  },
  {
    "path": "scripts/build/build-upload-a-docker-image.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\nprint_help() {\n  echo \"Usage: $0 [-c] [-D] [-h] [-l] [-o] [-p platforms]\"\n  echo \"-h: Print help\"\n  echo \"-b: add base_image and debug_image arguments to the build command\"\n  echo \"-c: name of the component to build\"\n  echo \"-d: directory for the Dockerfile\"\n  echo \"-f: override the name of the Dockerfile (-d still respected)\"\n  echo \"-o: overwrite image in the target remote repository even if the semver tag already exists\"\n  echo \"-p: Comma-separated list of platforms to build for (default: all supported)\"\n  echo \"-t: Release target (release|debug) if required by the Dockerfile\"\n  exit 1\n}\n\necho \"BRANCH=${BRANCH:?'expecting BRANCH env var'}\"\nbase_debug_img_arg=\"\"\ndocker_file_arg=\"Dockerfile\"\ntarget_arg=\"\"\nlocal_test_only='N'\nplatforms=\"linux/$(go env GOARCH)\"\nnamespace=\"jaegertracing\"\noverwrite='N'\nupload_readme='N'\n\nwhile getopts \"bc:d:f:hlop:t:\" opt; do\n\t# shellcheck disable=SC2220 # we don't need a *) case\n\tcase \"${opt}\" in\n\tb)\n\t\tbase_debug_img_arg=\"--build-arg base_image=localhost:5000/baseimg_alpine:latest --build-arg debug_image=localhost:5000/debugimg_alpine:latest \"\n\t\t;;\n\tc)\n\t\tcomponent_name=${OPTARG}\n\t\t;;\n\td)\n\t\tdir_arg=${OPTARG}\n\t\t;;\n\tf)\n\t\tdocker_file_arg=${OPTARG}\n\t\t;;\n\tl)\n\t\tlocal_test_only='Y'\n\t\t;;\n\to)\n\t\toverwrite='Y'\n\t\t;;\n\tp)\n\t\tplatforms=${OPTARG}\n\t\t;;\n\tt)\n\t\ttarget_arg=${OPTARG}\n\t\t;;\n\t?)\n\t\tprint_help\n\t\t;;\n\tesac\ndone\n\nset -x\n\nif [ -n \"${target_arg}\" ]; then\n    target_arg=\"--target ${target_arg}\"\nfi\n\ndocker_file_arg=\"${dir_arg}/${docker_file_arg}\"\n\ncheck_overwrite() {\n  for image in \"$@\"; do\n    if [[ \"$image\" == \"--tag\" ]]; then\n      continue\n    fi\n    if [[ $image =~ -snapshot ]]; then\n      continue\n    fi\n    tag=${image#*:}\n    if [[ $tag =~ ^[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?$ ]]; then\n      echo \"Checking if image $image already exists\"\n      if docker manifest inspect \"$image\" >/dev/null 2>&1; then\n        echo \"❌ ERROR: Image $image already exists and overwrite=$overwrite\"\n        exit 1\n      fi\n    fi\n  done\n}\n\nupload_comment=\"\"\n\nif [[ \"${local_test_only}\" = \"Y\" ]]; then\n    IMAGE_TAGS=(\"--tag\" \"localhost:5000/${namespace}/${component_name}:${GITHUB_SHA}\")\n    PUSHTAG=\"type=image,push=true\"\nelse\n    echo \"::group:: compute tags ${component_name}\"\n    # shellcheck disable=SC2086\n    IFS=\" \" read -r -a IMAGE_TAGS <<< \"$(bash scripts/utils/compute-tags.sh ${namespace}/${component_name})\"\n    echo \"::endgroup::\"\n\n    # Only push multi-arch images to dockerhub/quay.io for main branch or for release tags vM.N.P{-rcX}\n    if [[ \"$BRANCH\" == \"main\" || $BRANCH =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?$ ]]; then\n\t    echo \"will build docker images and upload to dockerhub/quay.io, BRANCH=$BRANCH\"\n\t    bash scripts/utils/docker-login.sh\n\t    PUSHTAG=\"type=image,push=true\"\n\t    upload_comment=\" and uploading\"\n\t    if [[ \"$overwrite\" == 'N' ]]; then\n\t      check_overwrite \"${IMAGE_TAGS[@]}\"\n\t    fi\n\t    upload_readme='Y'\n    else\n\t    echo 'skipping docker images upload, because not on tagged release or main branch'\n\t    PUSHTAG=\"type=image,push=false\"\n    fi\nfi\n\necho \"::group:: docker build ${component_name}\"\n# Some of the variables can be blank and should not produce extra arguments,\n# so we need to disable the linter checks for quoting.\n# TODO: collect arguments into an array and add optional once conditionally\n# shellcheck disable=SC2086\ndocker buildx build --output \"${PUSHTAG}\" ${target_arg} ${base_debug_img_arg} \\\n\t--progress=plain \\\n\t--platform=\"${platforms}\" \\\n\t--file \"${docker_file_arg}\" \\\n\t\"${IMAGE_TAGS[@]}\" \\\n\t\"${dir_arg}\"\necho \"::endgroup::\"\necho \"Finished building${upload_comment} ${component_name} ==============\"\n\nif [[ \"$upload_readme\" == \"Y\" ]]; then\n  echo \"::group:: docker upload ${dir_arg}/README.md\"\n  bash scripts/build/upload-docker-readme.sh \"${component_name}\" \"${dir_arg}\"/README.md\n  echo \"::endgroup::\"\nfi\n\necho \"::group:: docker prune\"\ndf -h /\ndocker buildx prune --all --force\ndocker system prune --force\ndf -h /\necho \"::endgroup::\"\n"
  },
  {
    "path": "scripts/build/build-upload-docker-images.sh",
    "content": "#!/bin/bash\n#\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\nprint_help() {\n  echo \"Usage: $0 [-B] [-D] [-h] [-l] [-o] [-p platforms]\"\n  echo \"-h: Print help\"\n  echo \"-B: Skip building of the binaries (e.g. when they were already built)\"\n  echo \"-D: Disable building of images with debugger\"\n  echo \"-l: Enable local-only mode that only pushes images to local registry\"\n  echo \"-o: overwrite image in the target remote repository even if the semver tag already exists\"\n  echo \"-p: Comma-separated list of platforms to build for (default: all supported)\"\n  exit 1\n}\n\nadd_debugger='Y'\nbuild_binaries='Y'\nplatforms=\"$(make echo-linux-platforms)\"\nFLAGS=()\n\nwhile getopts \"BDhlop:\" opt; do\n  case \"${opt}\" in\n  B)\n    build_binaries='N'\n    echo \"Will not build binaries as requested\"\n    ;;\n  D)\n    add_debugger='N'\n    echo \"Will not build debug images as requested\"\n    ;;\n  l)\n    # in the local-only mode the images will only be pushed to local registry\n    FLAGS=(\"${FLAGS[@]}\" -l)\n    ;;\n  o)\n    FLAGS=(\"${FLAGS[@]}\" -o)\n    ;;\n  p)\n    platforms=${OPTARG}\n    ;;\n  ?)\n    print_help\n    ;;\n  esac\ndone\n\nset -x\n\nif [[ \"$build_binaries\" == \"Y\" ]]; then\n  for platform in $(echo \"$platforms\" | tr ',' ' '); do\n    arch=${platform##*/}  # Remove everything before the last slash\n    make \"build-binaries-linux-$arch\"\n  done\nfi\n\nbaseimg_target='create-baseimg-debugimg'\nif [[ \"${add_debugger}\" == \"N\" ]]; then\n  baseimg_target='create-baseimg'\nfi\nmake \"$baseimg_target\" LINUX_PLATFORMS=\"$platforms\"\n\n# Helper function to build and upload docker images\n# Args: component_name, source_dir, [use_base_image], [build_debug]\nbuild_image() {\n  local component=$1\n  local dir=$2\n  local use_base_image=${3:-false}\n  local build_debug=${4:-false}\n\n  local base_flags=()\n  if [[ \"$use_base_image\" == \"true\" ]]; then\n    base_flags=(-b)\n  fi\n\n  local target_flags=()\n  if [[ \"$use_base_image\" == \"true\" ]]; then\n    target_flags=(-t release)\n  fi\n\n  bash scripts/build/build-upload-a-docker-image.sh \"${FLAGS[@]}\" \"${base_flags[@]}\" -c \"$component\" -d \"$dir\" -p \"${platforms}\" \"${target_flags[@]}\"\n\n  if [[ \"$build_debug\" == \"true\" ]] && [[ \"${add_debugger}\" == \"Y\" ]]; then\n    bash scripts/build/build-upload-a-docker-image.sh \"${FLAGS[@]}\" \"${base_flags[@]}\" -c \"${component}-debug\" -d \"$dir\" -p \"${platforms}\" -t debug\n  fi\n}\n\n# Build images with special handling for debug images\nbuild_image jaeger-remote-storage cmd/remote-storage true true\n\n# Build utility images\nbuild_image jaeger-es-index-cleaner cmd/es-index-cleaner true false\nbuild_image jaeger-es-rollover cmd/es-rollover true false\nbuild_image jaeger-cassandra-schema internal/storage/v1/cassandra/ false false\n\n# Build tool images\nbuild_image jaeger-tracegen cmd/tracegen false false\nbuild_image jaeger-anonymizer cmd/anonymizer false false\n"
  },
  {
    "path": "scripts/build/clean-binaries.sh",
    "content": "#!/bin/bash\n#\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nplatforms=$(make echo-platforms)\nfor main in ./cmd/*/main.go; do\n    dir=$(dirname \"$main\")\n    bin=$(basename \"$dir\")\n    rm -rf \"${dir:?}/$bin\"\n    for platform in $(echo \"$platforms\" | tr ',' ' ' | tr '/' '-'); do\n      b=\"${dir:?}/$bin-$platform\"\n      echo \"$b\"\n      rm -f \"$b\"\n      b=\"${dir:?}/$bin-debug-$platform\"\n      echo \"$b\"\n      rm -f \"$b\"\n    done\ndone\n"
  },
  {
    "path": "scripts/build/docker/base/Dockerfile",
    "content": "# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS cert\nRUN apk add --update --no-cache ca-certificates mailcap\n\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659\nCOPY --from=cert /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\nCOPY --from=cert /etc/mime.types /etc/mime.types\n"
  },
  {
    "path": "scripts/build/docker/debug/Dockerfile",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# We use a pre-built base image that includes Delve debugger.\n# See https://github.com/jaegertracing/base-image-with-debugger\n\nFROM ghcr.io/jaegertracing/base-image-with-debugger:0.1.0@sha256:dad2b5e8e26ea6d9092b8ecf4b582563376a8421fc44ed72f374b18ae840d5eb\n"
  },
  {
    "path": "scripts/build/docker/debug/go.mod",
    "content": "module debug-delve\n\ngo 1.26.0\n\nrequire github.com/go-delve/delve v1.26.0\n\nrequire (\n\tgithub.com/cilium/ebpf v0.11.0 // indirect\n\tgithub.com/cosiner/argv v0.1.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect\n\tgithub.com/derekparker/trie/v3 v3.2.0 // indirect\n\tgithub.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 // indirect\n\tgithub.com/google/go-dap v0.12.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.13 // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/spf13/cobra v1.9.1 // indirect\n\tgithub.com/spf13/pflag v1.0.6 // indirect\n\tgo.starlark.net v0.0.0-20231101134539-556fd59b42f6 // indirect\n\tgolang.org/x/arch v0.11.0 // indirect\n\tgolang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect\n\tgolang.org/x/sync v0.8.0 // indirect\n\tgolang.org/x/sys v0.26.0 // indirect\n\tgolang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "scripts/build/docker/debug/go.sum",
    "content": "github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=\ngithub.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=\ngithub.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=\ngithub.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4=\ngithub.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/derekparker/trie/v3 v3.2.0 h1:fET3Qbp9xSB7yc7tz6Y2GKMNl0SycYFo3cmiRI3Gpf0=\ngithub.com/derekparker/trie/v3 v3.2.0/go.mod h1:P94lW0LPgiaMgKAEQD59IDZD2jMK9paKok8Nli/nQbE=\ngithub.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=\ngithub.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/go-delve/delve v1.26.0 h1:YZT1kXD76mxba4/wr+tyUa/tSmy7qzoDsmxutT42PIs=\ngithub.com/go-delve/delve v1.26.0/go.mod h1:8BgFFOXTi1y1M+d/4ax1LdFw0mlqezQiTZQpbpwgBxo=\ngithub.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 h1:IGtvsNyIuRjl04XAOFGACozgUD7A82UffYxZt4DWbvA=\ngithub.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-dap v0.12.0 h1:rVcjv3SyMIrpaOoTAdFDyHs99CwVOItIJGKLQFQhNeM=\ngithub.com/google/go-dap v0.12.0/go.mod h1:tNjCASCm5cqePi/RVXXWEVqtnNLV1KTWtYOqu6rZNzc=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=\ngithub.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=\ngithub.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=\ngithub.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=\ngithub.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngo.starlark.net v0.0.0-20231101134539-556fd59b42f6 h1:+eC0F/k4aBLC4szgOcjd7bDTEnpxADJyWJE0yowgM3E=\ngo.starlark.net v0.0.0-20231101134539-556fd59b42f6/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM=\ngolang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=\ngolang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=\ngolang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=\ngolang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=\ngolang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=\ngolang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=\ngolang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5 h1:TCDqnvbBsFapViksHcHySl/sW4+rTGNIAoJJesHRuMM=\ngolang.org/x/telemetry v0.0.0-20241106142447-58a1122356f5/go.mod h1:8nZWdGp9pq73ZI//QJyckMQab3yq7hoWi7SI0UIusVI=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "scripts/build/docker/debug/tools.go",
    "content": "// Copyright (c) 2024 The Jaeger Authors.\n// SPDX-License-Identifier: Apache-2.0\n\n//go:build tools\n\npackage tools\n\n// This file follows the recommendation at\n// https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module\n// on how to pin tooling dependencies to a go.mod file.\n// This ensures that all systems use the same version of tools in addition to regular dependencies.\n\nimport (\n\t_ \"github.com/go-delve/delve/cmd/dlv\"\n)\n"
  },
  {
    "path": "scripts/build/package-deploy.sh",
    "content": "#!/bin/bash\n#\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\nset -euxf -o pipefail\n\n# This script uses --sort=name option that is not supported by MacOS tar.\n# On MacOS, install `brew install gnu-tar` and run this script with TARCMD=gtar.\nTARCMD=${TARCMD:-tar}\n\nprint_help() {\n  echo \"Usage: $0 [-h] [-k gpg_key_id] [-p platforms]\"\n  echo \"-h: Print help\"\n  echo \"-k: Override default GPG signing key ID. Use 'skip' to skip signing.\"\n  echo \"-p: Comma-separated list of platforms to build for (default: all supported)\"\n  exit 1\n}\n\n# Default signing key (accessible to maintainers-only), documented in https://www.jaegertracing.io/download/.\ngpg_key_id=\"B42D1DB0F079690F\"\nplatforms=\"$(make echo-platforms)\"\nwhile getopts \"hk:p:\" opt; do\n  case \"${opt}\" in\n  k)\n    gpg_key_id=${OPTARG}\n    ;;\n  p)\n    platforms=${OPTARG}\n    ;;\n  ?)\n    print_help\n    ;;\n  esac\ndone\n\n# stage-platform-files stages files for the platform ($1) into the package\n# staging dir ($2). If you pass in a file extension ($3) it will be used when\n# copying the source files\n\nfunction stage-platform-files {\n    local -r PLATFORM=$1\n    local -r PACKAGE_STAGING_DIR=$2\n    local -r FILE_EXTENSION=${3:-}\n\n    cp \"./cmd/jaeger/jaeger-${PLATFORM}\"          \"${PACKAGE_STAGING_DIR}/jaeger${FILE_EXTENSION}\"\n    cp \"./examples/hotrod/hotrod-${PLATFORM}\"     \"${PACKAGE_STAGING_DIR}/example-hotrod${FILE_EXTENSION}\"\n}\n\n# stage-tool-platform-files stages the different tool files in the platform ($1) into the package\n# staging dir ($2). If you pass in a file extension ($3) it will be used when\n# copying on the source\nfunction stage-tool-platform-files {\n    local -r PLATFORM=$1\n    local -r TOOLS_PACKAGE_STAGING_DIR=$2\n    local -r FILE_EXTENSION=${3:-}\n\n    cp \"./cmd/es-index-cleaner/es-index-cleaner-${PLATFORM}\"        \"${TOOLS_PACKAGE_STAGING_DIR}/jaeger-es-index-cleaner${FILE_EXTENSION}\"\n    cp \"./cmd/es-rollover/es-rollover-${PLATFORM}\"                  \"${TOOLS_PACKAGE_STAGING_DIR}/jaeger-es-rollover${FILE_EXTENSION}\"\n    cp \"./cmd/esmapping-generator/esmapping-generator-${PLATFORM}\"  \"${TOOLS_PACKAGE_STAGING_DIR}/jaeger-esmapping-generator${FILE_EXTENSION}\"\n}\n\n# package pulls built files for the platform ($2) and compresses it using the compression ($1).\n# If you pass in a file extension ($3) it will be look for binaries with that extension.\nfunction package {\n    local -r COMPRESSION=$1\n    local -r PLATFORM=$2\n    local -r FILE_EXTENSION=${3:-}\n    local -r PACKAGE_NAME=jaeger-${VERSION}-$PLATFORM\n    local -r TOOLS_PACKAGE_NAME=jaeger-tools-${VERSION}-$PLATFORM\n\n    echo \"Packaging binaries for $PLATFORM\"\n\n    PACKAGES=(\"$PACKAGE_NAME\" \"$TOOLS_PACKAGE_NAME\")\n    for d in \"${PACKAGES[@]}\"; do\n      if [ -d \"$d\" ]; then\n        rm -vrf \"$d\"\n      fi\n      mkdir \"$d\"\n    done\n    stage-platform-files \"$PLATFORM\" \"$PACKAGE_NAME\" \"$FILE_EXTENSION\"\n    stage-tool-platform-files \"$PLATFORM\" \"$TOOLS_PACKAGE_NAME\" \"$FILE_EXTENSION\"\n    # Create a checksum file for all the files being packaged in the archive. Sorted by filename.\n    for d in \"${PACKAGES[@]}\"; do\n      find \"$d\" -type f -exec shasum -b -a 256 {} \\; | sort -k2 | tee \"./deploy/$d.sha256sum.txt\"\n    done\n\n    if [ \"$COMPRESSION\" == \"zip\" ]\n    then\n      for d in \"${PACKAGES[@]}\"; do\n        local ARCHIVE_NAME=\"$d.zip\"\n        echo \"Packaging into $ARCHIVE_NAME:\"\n        zip -r \"./deploy/$ARCHIVE_NAME\" \"$d\"\n      done\n    else\n      for d in \"${PACKAGES[@]}\"; do\n        local ARCHIVE_NAME=\"$d.tar.gz\"\n        echo \"Packaging into $ARCHIVE_NAME:\"\n        ${TARCMD} --sort=name -czvf \"./deploy/$ARCHIVE_NAME\" \"$d\"\n      done\n    fi\n    for d in \"${PACKAGES[@]}\"; do\n      rm -vrf \"$d\"\n    done\n}\n\nVERSION=\"$(make echo-version | perl -lne 'print $1 if /^v(\\d+.\\d+.\\d+(-rc\\d+)?)$/' )\"\necho \"Working on version: $VERSION\"\nif [ -z \"$VERSION\" ]; then\n    # We want to halt if for some reason the version string is empty as this is an obvious error case\n    >&2 echo 'Failed to detect a version string'\n    exit 1\nfi\n\n# make needed directories\nrm -rf deploy\nmkdir deploy\n\n# Loop through each platform (separated by commas)\nfor platform in $(echo \"$platforms\" | tr ',' ' '); do\n  os=${platform%%/*}  # Remove everything after the slash\n  arch=${platform##*/}  # Remove everything before the last slash\n  if [[ \"$os\" == \"windows\" ]]; then\n    package tar \"${os}-${arch}\" .exe\n    package zip \"${os}-${arch}\" .exe\n  else\n    package tar \"${os}-${arch}\"\n  fi\ndone\n\n# Create a checksum file for all non-checksum files in the deploy directory. Strips the leading 'deploy/' directory from filepaths. Sort by filename.\nfind deploy \\( ! -name '*sha256sum.txt' \\) -type f -exec shasum -b -a 256 {} \\; \\\n  | sed -r 's#(\\w+\\s+\\*?)deploy/(.*)#\\1\\2#' \\\n  | sort -k2 \\\n  | tee \"./deploy/jaeger-${VERSION}.sha256sum.txt\"\n\n# Use gpg to sign the (g)zip files (excluding checksum files) into .asc files.\nif [[ \"${gpg_key_id}\" == \"skip\" ]]; then\n  echo \"Skipping GPG signing as requested\"\nelse\n  echo \"Signing archives with GPG key ${gpg_key_id}\"\n  gpg --list-keys \"${gpg_key_id}\"\n  find deploy \\( ! -name '*sha256sum.txt' \\) -type f -exec gpg -v --local-user \"${gpg_key_id}\" --armor --detach-sign {} \\;\nfi\n\n# show your work\nls -lF deploy/\n"
  },
  {
    "path": "scripts/build/rebuild-ui.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euxf -o pipefail\n\ncd jaeger-ui\n\nif [[ \"$(git rev-parse --is-shallow-repository)\" == \"true\" ]]; then\n    git fetch --unshallow\nfi\ngit fetch --all --tags\ngit log --oneline --decorate=full -n 10 | cat\n\nlast_tag=$(git describe --tags --dirty 2>/dev/null)\n\nif [[ \"$last_tag\" =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+$ ]];  then\n    branch_hash=$(git rev-parse HEAD)\n    last_tag_hash=$(git rev-parse \"$last_tag\")\n\n    if [[ \"$branch_hash\" == \"$last_tag_hash\" ]]; then\n        temp_file=$(mktemp)\n        # shellcheck disable=SC2064\n        trap \"rm -f ${temp_file}\" EXIT\n        release_url=\"https://github.com/jaegertracing/jaeger-ui/releases/download/${last_tag}/assets.tar.gz\"\n        if curl --silent --fail --location --output \"$temp_file\" \"$release_url\"; then\n\n            mkdir -p packages/jaeger-ui/build/\n            rm -r -f packages/jaeger-ui/build/\n            tar -zxvf \"$temp_file\" packages/jaeger-ui/build/\n            exit 0\n        fi\n    fi\nfi\n\n# do a regular full build\nnvm use\nnpm ci && cd packages/jaeger-ui && npm run build\n"
  },
  {
    "path": "scripts/build/upload-docker-readme.sh",
    "content": "#!/bin/bash\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\nusage() {\n  echo \"Usage: $0 <repository_name> <file_path>\"\n  exit 1\n}\n\nif [ \"$#\" -ne 2 ]; then\n  echo \"🛑 Error: Missing arguments.\"\n  usage\nfi\n\nrepo=\"$1\"\nreadme_path=\"$2\"\n\n# Check if README file exists before calling realpath\nif [ ! -f \"$readme_path\" ]; then\n  echo \"🟡 Warning: no README file found at path $readme_path\"\n  exit 0\nfi\n\nabs_readme_path=$(realpath \"$readme_path\")\nrepository=\"jaegertracing/$repo\"\n\nDOCKERHUB_TOKEN=${DOCKERHUB_TOKEN:?'missing Docker Hub token'}\nDOCKERHUB_USERNAME=${DOCKERHUB_USERNAME:-\"jaegertracingbot\"}\nQUAY_TOKEN=${QUAY_TOKEN:?'missing Quay token'}\n\ndockerhub_url=\"https://hub.docker.com/v2/repositories/$repository/\"\nquay_url=\"https://quay.io/api/v1/repository/${repository}\"\n\nreadme_content=$(<\"$abs_readme_path\")\n\n# 🛑 IMPORTANT: do not echo commands as they contain tokens\nset +x\n\n# Handle DockerHUB upload\n\n# Get Docker Hub JWT token from PAT\ndockerhub_credentials=$(jq -n \\\n  --arg pwd \"$DOCKERHUB_TOKEN\" \\\n  --arg user \"$DOCKERHUB_USERNAME\" \\\n  '{username: $user, password: $pwd}')\ndockerhub_jwt=$(curl -s -H \"Content-Type: application/json\" \\\n  -X POST -d \"$dockerhub_credentials\" \\\n  https://hub.docker.com/v2/users/login/ | jq -r .token)\n\nif [ \"$dockerhub_jwt\" = \"null\" ] || [ -z \"$dockerhub_jwt\" ]; then\n  echo \"🛑 Failed to get Docker Hub JWT token\"\n  exit 1\nfi\n\n# encode readme as properly escaped JSON\nbody=$(jq -n \\\n  --arg full_desc \"$readme_content\" \\\n  '{full_description: $full_desc}')\n\ndockerhub_response=$(curl -s -w \"%{http_code}\" -X PATCH \"$dockerhub_url\" \\\n    -H \"Content-Type: application/json\" \\\n    -H \"Authorization: Bearer $dockerhub_jwt\" \\\n    -d \"$body\")\n\nhttp_code=\"${dockerhub_response: -3}\"\nresponse_body=\"${dockerhub_response:0:${#dockerhub_response}-3}\"\n\nif [ \"$http_code\" -eq 200 ]; then\n  echo \"✅ Successfully updated Docker Hub README for $repository\"\nelse\n  echo \"🛑 Failed to update Docker Hub README for $repository with status code $http_code\"\n  echo \"🛑 Full response: $response_body\"\nfi\n\n# Handle Quay upload\n\n# encode readme as properly escaped JSON\nquay_body=$(jq -n \\\n  --arg full_desc \"$readme_content\" \\\n  '{description: $full_desc}')\n\nquay_response=$(curl -s -w \"%{http_code}\" -X PUT \"$quay_url\" \\\n    -H \"Content-Type: application/json\" \\\n    -H \"Authorization: Bearer $QUAY_TOKEN\" \\\n    -d \"$quay_body\")\n\nquay_http_code=\"${quay_response: -3}\"\nquay_response_body=\"${quay_response:0:${#quay_response}-3}\"\n\nif [ \"$quay_http_code\" -eq 200 ]; then\n  echo \"✅ Successfully updated Quay.io README for $repository\"\nelse\n  echo \"🛑 Failed to update Quay.io README for $repository with status code $quay_http_code\"\n  echo \"🛑 Full response: $quay_response_body\"\nfi\n"
  },
  {
    "path": "scripts/e2e/adaptive-sampling-integration-test.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\n# This script is currently a placeholder.\n\n# Commands to run integration test:\n#   SAMPLING_STORAGE_TYPE=memory SAMPLING_CONFIG_TYPE=adaptive go run -tags=ui ./cmd/all-in-one --log-level=debug\n#   go run ./cmd/tracegen -adaptive-sampling=http://localhost:14268/api/sampling -pause=10ms -duration=60m\n\n# Check how strategy is changing\n#   curl 'http://localhost:14268/api/sampling?service=tracegen' | jq .\n\n# Issues\n# - SDK does not report sampling probability in the tags the way Jaeger SDKs did\n# - Server probably does not recognize spans as having adaptive sampling without sampler info\n# - There is no way to modify target traces-per-second dynamically, must restart collector.\n"
  },
  {
    "path": "scripts/e2e/cassandra.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euxf -o pipefail\n\nexport CASSANDRA_USERNAME=\"cassandra\"\nexport CASSANDRA_PASSWORD=\"cassandra\"\nsuccess=\"false\"\ntimeout=600\nend_time=$((SECONDS + timeout))\n\nSKIP_APPLY_SCHEMA=${SKIP_APPLY_SCHEMA:-\"false\"}\nexport CASSANDRA_CREATE_SCHEMA=${SKIP_APPLY_SCHEMA}\n\nusage() {\n  echo $\"Usage: $0 <cassandra_version> <schema_version> <storage_test>\"\n  echo \"  storage_test: direct | e2e\"\n  exit 1\n}\n\ncheck_arg() {\n  if [ ! $# -eq 3 ]; then\n    echo \"ERROR: need exactly three arguments, <cassandra_version> <schema_version> <storage_test>\"\n    usage\n  fi\n}\n\nsetup_cassandra() {\n  local compose_file=$1\n  docker compose -f \"$compose_file\" up -d\n}\n\nhealthcheck_cassandra() {\n  local cas_version=$1\n  local container_name=\"cassandra-${cas_version}\"\n  # Since the healthcheck in cassandra is done at the interval of 30s\n  local wait_seconds=30\n\n  while [ $SECONDS -lt $end_time ]; do\n    status=$(docker inspect -f '{{ .State.Health.Status }}' \"${container_name}\")\n    if [[ ${status} == \"healthy\" ]]; then\n      echo \"✅ $container_name is healthy\"\n      return 0\n    fi\n    echo \"Waiting for $container_name to be healthy. Current status: $status\"\n    sleep $wait_seconds\n  done\n\n  echo \"❌ ERROR: $container_name did not become healthy in time\"\n  exit 1\n}\n\ndump_logs() {\n  local compose_file=$1\n  echo \"::group::🚧 🚧 🚧 Cassandra logs\"\n  docker compose -f \"${compose_file}\" logs\n  echo \"::endgroup::\"\n}\n\nteardown_cassandra() {\n  local compose_file=$1\n   if [[ \"$success\" == \"false\" ]]; then\n    dump_logs \"${compose_file}\"\n  fi\n  docker compose -f \"$compose_file\" down\n}\n\napply_schema() {\n  local image=cassandra-schema\n  local schema_dir=internal/storage/v1/cassandra/\n  local schema_version=$1\n  local keyspace=$2\n  local params=(\n    --rm\n    --env CQLSH_HOST=localhost\n    --env CQLSH_PORT=9042\n    --env \"TEMPLATE=/cassandra-schema/${schema_version}.cql.tmpl\"\n    --env \"KEYSPACE=${keyspace}\"\n    --env \"CASSANDRA_USERNAME=${CASSANDRA_USERNAME}\"\n    --env \"CASSANDRA_PASSWORD=${CASSANDRA_PASSWORD}\"\n    --network host\n  )\n  docker build -t ${image} ${schema_dir}\n  docker run \"${params[@]}\" ${image}\n}\n\nrun_integration_test() {\n  local version=$1\n  local major_version=${version%%.*}\n  local schema_version=$2\n  local storageTest=$3\n  local primaryKeyspace=\"jaeger_v1_dc1\"\n  local archiveKeyspace=\"jaeger_v1_dc1_archive\"\n  local compose_file=\"docker-compose/cassandra/v$major_version/docker-compose.yaml\"\n\n  setup_cassandra \"${compose_file}\"\n\n  # shellcheck disable=SC2064\n  trap \"teardown_cassandra ${compose_file}\" EXIT\n\n  healthcheck_cassandra \"${major_version}\"\n\n  if [ \"${SKIP_APPLY_SCHEMA}\" = \"false\" ]; then\n    apply_schema \"$schema_version\" \"$primaryKeyspace\"\n    apply_schema \"$schema_version\" \"$archiveKeyspace\"\n  fi\n\n  if [ \"${storageTest}\" = \"direct\" ]; then\n    STORAGE=cassandra make storage-integration-test\n  elif [ \"${storageTest}\" == \"e2e\" ]; then\n    STORAGE=cassandra make jaeger-v2-storage-integration-test\n  else\n    echo \"Unknown storage_test value $storageTest. Valid options are direct or e2e\"\n    exit 1\n  fi\n  success=\"true\"\n}\n\n\nmain() {\n  check_arg \"$@\"\n\n  echo \"Executing integration test for $1 with schema $2.cql.tmpl\"\n  run_integration_test \"$1\" \"$2\" \"$3\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "scripts/e2e/clickhouse.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euxf -o pipefail\n\nsuccess=\"false\"\ntimeout=600\nend_time=$((SECONDS + timeout))\ncompose_file=\"docker-compose/clickhouse/docker-compose.yml\"\ncontainer_name=\"clickhouse\"\n\nsetup_clickhouse() {\n    echo \"Starting ClickHouse with $compose_file\"\n    docker compose -f \"$compose_file\" up -d\n}\n\nhealthcheck_clickhouse() {\n    local wait_seconds=10\n\n    while [ $SECONDS -lt $end_time ]; do\n        status=$(docker inspect -f '{{ .State.Health.Status }}' \"${container_name}\")\n        if [[ ${status} == \"healthy\" ]]; then\n            echo \"✅ $container_name is healthy\"\n            return 0\n        fi\n        echo \"Waiting for $container_name to be healthy. Current status: $status\"\n        sleep $wait_seconds\n    done\n\n    echo \"❌ ERROR: $container_name did not become healthy in time\"\n    exit 1\n}\n\ndump_logs() {\n    echo \"::group::🚧 🚧 🚧 Clickhouse logs\"\n    docker compose -f \"${compose_file}\" logs\n    echo \"::endgroup::\"\n}\n\nteardown_clickhouse() {\n    if [[ \"$success\" == \"false\" ]]; then\n        dump_logs \"${compose_file}\"\n    fi\n    docker compose -f \"$compose_file\" down\n}\n\nrun_integration_test() {\n    setup_clickhouse\n    trap teardown_clickhouse EXIT\n    healthcheck_clickhouse\n    STORAGE=clickhouse make jaeger-v2-storage-integration-test\n    success=\"true\"\n}\n\nmain() {\n    echo \"Executing ClickHouse integration tests\"\n    run_integration_test\n}\n\nmain"
  },
  {
    "path": "scripts/e2e/compare_metrics.py",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport argparse\nimport sys\nfrom difflib import unified_diff\nfrom bisect import insort\nfrom prometheus_client.parser import text_string_to_metric_families\nimport re\n\nEXCLUDED_LABELS = {'service_instance_id', 'otel_scope_version', 'otel_scope_schema_url'}\n\n# Configuration for transient labels that should be normalized during comparison\nTRANSIENT_LABEL_PATTERNS = {\n    'kafka': {\n        'topic': {\n            'pattern': r'jaeger-spans-\\d+',\n            'replacement': 'jaeger-spans-'\n        }\n    },\n    # Add more patterns here as needed\n    # Example:\n    # 'elasticsearch': {\n    #     'index': {\n    #         'pattern': r'jaeger-\\d{4}-\\d{2}-\\d{2}',\n    #         'replacement': 'jaeger-YYYY-MM-DD'\n    #     }\n    # }\n}\n\nMETRIC_EXCLUSION_RULES = {\n    # excluding HTTP 5xx responses as these can be flaky\n    'http_5xx_errors': {\n        'condition': 'label_match',\n        'label': 'http_response_status_code',\n        'pattern': r'^5\\d{2}$',\n    },\n    \n}\n\ndef should_exclude_metric(metric_name, labels):\n    \"\"\"\n    Determines if a metric should be excluded from comparison based on configured rules.\n    \n    Args:\n        metric_name: The name of the metric\n        labels: Dictionary of labels for the metric\n        \n    Returns:\n        tuple: (should_exclude: bool, reason: str or None)\n    \"\"\"\n    for rule_name, rule_config in METRIC_EXCLUSION_RULES.items():\n        condition = rule_config['condition']\n        \n        if condition == 'label_match':\n            label = rule_config['label']\n            pattern = rule_config['pattern']\n            if label in labels and re.match(pattern, labels[label]):\n                return True\n    return False\n\n\ndef suppress_transient_labels(metric_name, labels):\n    \"\"\"\n    Suppresses transient labels in metrics based on configured patterns.\n    \n    Args:\n        metric_name: The name of the metric\n        labels: Dictionary of labels for the metric\n        \n    Returns:\n        Dictionary of labels with transient values normalized\n    \"\"\"\n    labels_copy = labels.copy()\n    \n    for service_pattern, label_configs in TRANSIENT_LABEL_PATTERNS.items():\n        if service_pattern in metric_name:\n            for label_name, pattern_config in label_configs.items():\n                if label_name in labels_copy:\n                    pattern = pattern_config['pattern']\n                    replacement = pattern_config['replacement']\n                    labels_copy[label_name] = re.sub(pattern, replacement, labels_copy[label_name])\n    \n    return labels_copy\n\ndef read_metric_file(file_path):\n    with open(file_path, 'r') as f:\n        return f.readlines()\n    \ndef parse_metrics(content):\n    metrics = []\n    metrics_exclusion_count = 0\n    for family in text_string_to_metric_families(content):\n        for sample in family.samples:\n            labels = dict(sample.labels)\n\n            if should_exclude_metric(sample.name, labels):\n                metrics_exclusion_count += 1\n                continue\n\n            # Remove undesirable metric labels to match the diff generation\n            for label in EXCLUDED_LABELS:\n                labels.pop(label, None)\n            labels = suppress_transient_labels(sample.name, labels)\n            \n            label_pairs = sorted(labels.items(), key=lambda x: x[0])\n            label_str = ','.join(f'{k}=\"{v}\"' for k,v in label_pairs)\n            metric = f\"{family.name}{{{label_str}}}\"\n            insort(metrics , metric)\n        \n    return metrics,metrics_exclusion_count\n\n\ndef generate_diff(file1_content, file2_content):\n    if isinstance(file1_content, list):\n        file1_content = ''.join(file1_content)\n    if isinstance(file2_content, list):\n        file2_content = ''.join(file2_content)\n\n    metrics1,excluded_metrics_count1 = parse_metrics(file1_content)\n    metrics2,excluded_metrics_count2 = parse_metrics(file2_content)\n\n    diff = unified_diff(metrics1, metrics2,lineterm='',n=0)\n    total_excluded = excluded_metrics_count1 + excluded_metrics_count2\n    \n    exclusion_lines = ''\n    if total_excluded > 0:\n        exclusion_lines = f'\\nMetrics excluded from A: {excluded_metrics_count1}\\nMetrics excluded from B: {excluded_metrics_count2}'\n    \n    return '\\n'.join(diff) + exclusion_lines\n\ndef write_diff_file(diff_lines, output_path):\n    \n    with open(output_path, 'w') as f:\n        f.write(diff_lines)\n        f.write('\\n')  # Add final newline\n        print(f\"Diff file successfully written to: {output_path}\")\n\ndef main():\n    parser = argparse.ArgumentParser(description='Generate diff between two Jaeger metric files')\n    parser.add_argument('--file1', help='Path to first metric file')\n    parser.add_argument('--file2', help='Path to second metric file')\n    parser.add_argument('--output', '-o', default='metrics_diff.txt',\n                       help='Output diff file path (default: metrics_diff.txt)')\n    \n    args = parser.parse_args()\n    \n    # Read input files\n    file1_lines = read_metric_file(args.file1)\n    file2_lines = read_metric_file(args.file2)\n    \n    # Generate diff\n    diff_lines = generate_diff(file1_lines, file2_lines)\n    \n    # Check if there are any differences\n    if diff_lines:\n        print(\"differences found between the metric files.\")\n        print(\"=== Metrics Comparison Results ===\")\n        print(diff_lines)\n        write_diff_file(diff_lines, args.output)\n        return 1\n\n    print(\"no difference found\")\n    return 0\n\nif __name__ == '__main__':\n    sys.exit(main())\n"
  },
  {
    "path": "scripts/e2e/elasticsearch.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nPS4='T$(date \"+%H:%M:%S\") '\nset -euf -o pipefail\n\n# use global variables to reflect status of db\ndb_is_up=\nsuccess=\"false\"\n\nusage() {\n  echo \"Usage: $0 <backend> <backend_version> <storage_test>\"\n  echo \"  backend:         elasticsearch | opensearch\"\n  echo \"  backend_version: major version, e.g. 7.x\"\n  echo \"  storage_test:    direct | e2e\"\n  exit 1\n}\n\ncheck_arg() {\n  if [ ! $# -eq 3 ]; then\n    echo \"ERROR: need exactly three arguments\"\n    usage\n  fi\n}\n\n# start the elasticsearch/opensearch container\nsetup_db() {\n  local compose_file=$1\n  docker compose -f \"${compose_file}\" up -d\n}\n\n# check if the storage is up and running\nwait_for_storage() {\n  local distro=$1\n  local url=$2\n  local compose_file=$3\n  local params=(\n    --silent\n    --output\n    /dev/null\n    --write-out\n    \"%{http_code}\"\n  )\n  local max_attempts=60\n  local attempt=0\n  echo \"Waiting for ${distro} to be available at ${url}...\"\n  until [[ \"$(curl \"${params[@]}\" \"${url}\")\" == \"200\" ]] || (( attempt >= max_attempts )); do\n    attempt=$(( attempt + 1 ))\n    echo \"Attempt: ${attempt} ${distro} is not yet available at ${url}...\"\n    sleep 10\n  done\n\n  # if after all the attempts the storage is not accessible, terminate it and exit\n  if [[ \"$(curl \"${params[@]}\" \"${url}\")\" != \"200\" ]]; then\n    echo \"ERROR: ${distro} is not ready at ${url} after $(( attempt * 10 )) seconds\"\n    db_is_up=0\n  else\n    echo \"SUCCESS: ${distro} is available at ${url}\"\n    db_is_up=1\n  fi\n}\n\nbring_up_storage() {\n  local distro=$1\n  local version=$2\n  local major_version=${version%%.*}\n  local compose_file=\"docker-compose/${distro}/v${major_version}/docker-compose.yml\"\n\n  echo \"starting ${distro} ${major_version}\"\n  for retry in 1 2 3\n  do\n    echo \"attempt $retry\"\n    if [ \"${distro}\" = \"elasticsearch\" ] || [ \"${distro}\" = \"opensearch\" ]; then\n        setup_db \"${compose_file}\"\n    else\n      echo \"Unknown distribution $distro. Valid options are opensearch or elasticsearch\"\n      usage\n    fi\n    wait_for_storage \"${distro}\" \"http://localhost:9200\" \"${compose_file}\"\n    if [ ${db_is_up} = \"1\" ]; then\n      break\n    fi\n  done\n  # shellcheck disable=SC2064\n  trap \"teardown_storage ${compose_file} ${distro}\" EXIT\n  if [ ${db_is_up} != \"1\" ]; then\n    echo \"ERROR: unable to start ${distro}\"\n    exit 1\n  fi\n}\n\ndump_logs() {\n  local compose_file=$1\n  local distro=$2\n  echo \"::group::${distro} logs\"\n  docker compose -f \"${compose_file}\" logs\n  echo \"::endgroup::\"\n}\n\n# terminate the elasticsearch/opensearch container\nteardown_storage() {\n  local compose_file=$1\n  local distro=$2\n  if [[ \"$success\" == \"false\" ]]; then\n    dump_logs \"${compose_file}\" \"${distro}\"\n  fi\n  docker compose -f \"${compose_file}\" down\n}\n\nbuild_local_img(){\n    make build-es-index-cleaner GOOS=linux\n    make build-es-rollover GOOS=linux\n    make create-baseimg PLATFORMS=\"linux/$(go env GOARCH)\"\n    #build es-index-cleaner and es-rollover images\n    GITHUB_SHA=local-test BRANCH=local-test bash scripts/build/build-upload-a-docker-image.sh -l -b -c jaeger-es-index-cleaner -d cmd/es-index-cleaner -t release -p \"linux/$(go env GOARCH)\"\n    GITHUB_SHA=local-test BRANCH=local-test bash scripts/build/build-upload-a-docker-image.sh -l -b -c jaeger-es-rollover -d cmd/es-rollover -t release -p \"linux/$(go env GOARCH)\"\n}\n\nmain() {\n  check_arg \"$@\"\n  local distro=$1\n  local es_version=$2\n  local storage_test=$3\n\n  set -x\n\n  bring_up_storage \"${distro}\" \"${es_version}\"\n  build_local_img\n  if [[ \"${storage_test}\" == \"e2e\" ]]; then\n    STORAGE=${distro} SPAN_STORAGE_TYPE=${distro} make jaeger-v2-storage-integration-test\n  elif [[ \"${storage_test}\" == \"direct\" ]]; then\n    STORAGE=${distro} make storage-integration-test\n    make index-cleaner-integration-test\n    make index-rollover-integration-test\n  else\n    echo \"ERROR: Invalid argument value storage_test=${storage_test}, expecting direct or e2e\"\n    exit 1\n  fi\n  success=\"true\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "scripts/e2e/filter_coverage.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) 2026 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n#\n# Filters a Go coverage profile in-place by applying the same exclusions defined\n# in .codecov.yml so coverage metrics stay in sync between this gate and Codecov.\n#\n# Usage:\n#   python3 scripts/e2e/filter_coverage.py <coverage.out> [path/to/.codecov.yml]\n\nimport fnmatch\nimport os\nimport sys\n\n\ndef load_exclusions(codecov_path: str) -> list[str]:\n    \"\"\"Return raw glob patterns from the ignore: section of .codecov.yml.\"\"\"\n    patterns = []\n    in_ignore = False\n    with open(codecov_path) as f:\n        for line in f:\n            stripped = line.strip()\n            if stripped == 'ignore:':\n                in_ignore = True\n            elif in_ignore:\n                if stripped.startswith('#'):\n                    continue\n                if stripped.startswith('- '):\n                    patterns.append(stripped[2:].strip('\"').strip(\"'\"))\n                elif stripped and not line[0].isspace():\n                    in_ignore = False\n    return patterns\n\n\ndef read_module_path(codecov_path: str) -> str:\n    \"\"\"\n    Read the Go module path so we can strip it from coverage import paths\n    to produce repo-relative paths that match the .codecov.yml patterns.\n    \"\"\"\n    go_mod_path = os.path.join(os.path.dirname(codecov_path), 'go.mod')\n    with open(go_mod_path) as f:\n        for line in f:\n            if line.startswith('module '):\n                return line.split()[1].strip()\n    raise ValueError(f'no module directive found in {go_mod_path}')\n\n\ndef should_exclude(path: str, patterns: list[str]) -> bool:\n    \"\"\"Return True if path matches any exclusion pattern.\n\n    Patterns with wildcards are matched via fnmatch. Patterns without\n    wildcards are treated as plain path prefixes.\n    \"\"\"\n    for pattern in patterns:\n        if '*' in pattern or '?' in pattern:\n            if fnmatch.fnmatch(path, pattern):\n                return True\n        else:\n            if path.startswith(pattern):\n                return True\n    return False\n\n\ndef main() -> None:\n    if len(sys.argv) < 2:\n        print(f'usage: {sys.argv[0]} <coverage.out> [.codecov.yml]', file=sys.stderr)\n        sys.exit(1)\n\n    coverage_path = sys.argv[1]\n    codecov_path = sys.argv[2] if len(sys.argv) > 2 else '.codecov.yml'\n\n    try:\n        exclusions = load_exclusions(codecov_path)\n    except FileNotFoundError:\n        print(f'error: {codecov_path} not found', file=sys.stderr)\n        sys.exit(1)\n\n    module_prefix = read_module_path(codecov_path) + '/'\n    kept = skipped = 0\n    kept_lines = []\n    with open(coverage_path) as f:\n        for line in f:\n            if line.startswith('mode:'):\n                kept_lines.append(line)\n                continue\n            # Coverage lines: \"github.com/.../file.go:line.col,line.col stmts count\"\n            # Extract the file path (everything before the first colon).\n            import_path = line.split(':')[0]\n            # Strip module prefix to get a repo-relative path for matching.\n            if import_path.startswith(module_prefix):\n                path = import_path[len(module_prefix):]\n            else:\n                path = import_path\n            if should_exclude(path, exclusions):\n                skipped += 1\n            else:\n                kept_lines.append(line)\n                kept += 1\n\n    with open(coverage_path, 'w') as f:\n        f.writelines(kept_lines)\n\n    print(f'filter_coverage: kept {kept}, excluded {skipped} lines', file=sys.stderr)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/e2e/kafka.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\ncompose_file=\"\"\nkafka_version=\"v3\"\nmanage_kafka=\"true\"\nsuccess=\"false\"\n\nusage() {\n  echo \"Usage: $0 [-S] [-v <kafka_version>]\"\n  echo \"  -S: 'no storage' - do not start or stop Kafka container (useful for local testing)\"\n  echo \"  -v: kafka major version (3.x); default: 3.x\"\n  exit 1\n}\n\nparse_args() {\n  while getopts \"v:Sh\" opt; do\n    case \"${opt}\" in\n    v)\n      case ${OPTARG} in\n      3.x)\n        kafka_version=\"v3\"\n        ;;\n      2.x)\n        kafka_version=\"v2\"\n        ;;\n      *)\n        echo \"Error: Invalid Kafka version. Valid options are 3.x or 2.x\"\n        usage\n        ;;\n      esac\n      ;;\n    S)\n      manage_kafka=\"false\"\n      ;;\n    *)\n      usage\n      ;;\n    esac\n  done\n  compose_file=\"docker-compose/kafka/${kafka_version}/docker-compose.yml\"\n}\n\nsetup_kafka() {\n  echo \"Starting Kafka using Docker Compose...\"\n  docker compose -f \"${compose_file}\" up -d kafka\n}\n\ndump_logs() {\n  echo \"::group::🚧 🚧 🚧 Kafka logs\"\n  docker compose -f \"${compose_file}\" logs\n  echo \"::endgroup::\"\n}\n\nteardown_kafka() {\n   if [[ \"$success\" == \"false\" ]]; then\n    dump_logs\n  fi\n  echo \"Stopping Kafka...\"\n  docker compose -f \"${compose_file}\" down\n}\n\nis_kafka_ready() {\n  docker compose -f \"${compose_file}\" \\\n    exec kafka /opt/kafka/bin/kafka-topics.sh \\\n    --list \\\n    --bootstrap-server localhost:9092 \\\n    >/dev/null 2>&1\n}\n\nwait_for_kafka() {\n  local timeout=180\n  local interval=5\n  local end_time=$((SECONDS + timeout))\n\n  while [ $SECONDS -lt $end_time ]; do\n    if is_kafka_ready; then\n      return\n    fi\n    echo \"Kafka broker not ready, waiting ${interval} seconds\"\n    sleep $interval\n  done\n\n  echo \"Timed out waiting for Kafka to start\"\n  exit 1\n}\n\nrun_integration_test() {\n  export STORAGE=kafka\n  make jaeger-v2-storage-integration-test\n}\n\nmain() {\n  parse_args \"$@\"\n\n  echo \"Executing Kafka integration test.\"\n  echo \"Kafka version ${kafka_version}.\"\n  set -x\n\n  if [[ \"$manage_kafka\" == \"true\" ]]; then\n    setup_kafka\n    trap 'teardown_kafka' EXIT\n  fi\n  wait_for_kafka\n\n  run_integration_test\n\n  success=\"true\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "scripts/e2e/metrics_summary.py",
    "content": "# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport argparse\nimport json\nfrom collections import defaultdict\n\ndef parse_diff_file(diff_path):\n    \"\"\"\n    Parses a unified diff file and categorizes changes into added, removed, and modified metrics.\n    Also captures the raw diff sections for each metric and the exclusion count.\n    \"\"\"\n    changes = {\n        'added': defaultdict(list),\n        'removed': defaultdict(list),\n        'modified': defaultdict(list)\n    }\n\n    # Store raw diff sections for each metric - just collect all lines related to each metric\n    raw_diff_sections = defaultdict(list)\n    exclusion_count = 0\n\n    with open(diff_path, 'r') as f:\n        lines = f.readlines()\n\n    current_metric = None\n    for line in lines:\n        original_line = line.rstrip('\\n')\n        stripped = original_line.strip()\n\n        if stripped.startswith('Metrics excluded from A: ') or stripped.startswith('Metrics excluded from B: '):\n            count_str = stripped.split(': ')[1]\n            exclusion_count += int(count_str)\n            continue\n        # Skip diff headers\n        if stripped.startswith('+++') or stripped.startswith('---'):\n            continue\n        # Check if this line contains a metric change\n        if stripped.startswith('+') or stripped.startswith('-'):\n            metric_name = extract_metric_name(stripped[1:].strip())\n            if metric_name:\n                # Track the change type\n                change_type = 'added' if stripped.startswith('+') else 'removed'\n                changes[change_type][metric_name].append(stripped[1:].strip())\n\n                # Always add to raw diff sections regardless of change type\n                raw_diff_sections[metric_name].append(original_line)\n                current_metric = metric_name\n            else:\n                # If we're in a metric section, keep adding lines\n                if current_metric:\n                    raw_diff_sections[current_metric].append(original_line)\n        elif stripped.startswith(' ') and current_metric:\n            # Context line - add to current metric's raw section\n            raw_diff_sections[current_metric].append(original_line)\n        else:\n            # End of current metric section\n            current_metric = None\n\n    # Identify modified metrics (same metric name with both additions and removals)\n    common_metrics = set(changes['added'].keys()) & set(changes['removed'].keys())\n    for metric in common_metrics:\n        changes['modified'][metric] = {\n            'added': changes['added'].pop(metric),\n            'removed': changes['removed'].pop(metric)\n        }\n\n    return changes, raw_diff_sections, exclusion_count\n\ndef extract_metric_name(line):\n    \"\"\"Extracts metric name from a metric line, matching the diff generation format\"\"\"\n    if '{' in line:\n        return line.split('{')[0].strip()\n    return line.strip()\n\ndef get_raw_diff_sample(raw_lines, max_lines=7):\n    \"\"\"\n    Get sample raw diff lines, preserving original diff formatting.\n    \"\"\"\n    if not raw_lines:\n        return []\n\n    # Take up to max_lines\n    sample_lines = raw_lines[:max_lines]\n    if len(raw_lines) > max_lines:\n        sample_lines.append(\"...\")\n\n    return sample_lines\n\ndef generate_diff_summary(changes, raw_diff_sections, exclusion_count):\n    \"\"\"\n    Generates a markdown summary from the parsed diff changes with raw diff samples.\n    \"\"\"\n    summary = []\n\n    # Statistics header\n    total_added = sum(len(v) for v in changes['added'].values())\n    total_removed = sum(len(v) for v in changes['removed'].values())\n    total_modified = len(changes['modified'])\n\n    summary.append(f\"**Total Changes:** {total_added + total_removed + total_modified}\\n\")\n    summary.append(f\"- 🆕 Added: {total_added} metrics\")\n    summary.append(f\"- ❌ Removed: {total_removed} metrics\")\n    summary.append(f\"- 🔄 Modified: {total_modified} metrics\")\n    summary.append(f\"- 🚫 Excluded: {exclusion_count} metrics\\n\")\n\n    # Added metrics\n    if changes['added']:\n        summary.append(\"\\n#### 🆕 Added Metrics\")\n        for metric, samples in changes['added'].items():\n            summary.append(f\"- `{metric}` ({len(samples)} variants)\")\n            raw_samples = get_raw_diff_sample(raw_diff_sections.get(metric, []))\n            if raw_samples:\n                summary.append(\"<details>\")\n                summary.append(\"<summary>View diff sample</summary>\")\n                summary.append(\"\")\n                summary.append(\"```diff\")\n                summary.extend(raw_samples)\n                summary.append(\"```\")\n                summary.append(\"</details>\")\n\n    # Removed metrics\n    if changes['removed']:\n        summary.append(\"\\n#### ❌ Removed Metrics\")\n        for metric, samples in changes['removed'].items():\n            summary.append(f\"- `{metric}` ({len(samples)} variants)\")\n            raw_samples = get_raw_diff_sample(raw_diff_sections.get(metric, []))\n            if raw_samples:\n                summary.append(\"<details>\")\n                summary.append(\"<summary>View diff sample</summary>\")\n                summary.append(\"\")\n                summary.append(\"```diff\")\n                summary.extend(raw_samples)\n                summary.append(\"```\")\n                summary.append(\"</details>\")\n\n    # Modified metrics\n    if changes['modified']:\n        summary.append(\"\\n#### 🔄 Modified Metrics\")\n        for metric, versions in changes['modified'].items():\n            summary.append(f\"- `{metric}`\")\n            summary.append(f\"  - Added variants: {len(versions['added'])}\")\n            summary.append(f\"  - Removed variants: {len(versions['removed'])}\")\n\n            raw_samples = get_raw_diff_sample(raw_diff_sections.get(metric, []))\n            if raw_samples:\n                summary.append(\"  <details>\")\n                summary.append(\"  <summary>View diff sample</summary>\")\n                summary.append(\"\")\n                summary.append(\"  ```diff\")\n                summary.extend([f\"  {line}\" for line in raw_samples])\n                summary.append(\"  ```\")\n                summary.append(\"  </details>\")\n\n    return \"\\n\".join(summary)\n\nMAX_METRIC_NAMES = 200\n\ndef generate_structured_json(changes):\n    \"\"\"\n    Generates a structured JSON-serializable dict of metric change data.\n    Contains only metric names (strings) and counts (ints) — no raw diff\n    lines or free-form text — so it is safe to pass through ci-summary.json\n    to the trusted publish workflow.\n\n    Counts use metric-name semantics (number of unique metric names per\n    category) so that they match the displayed metric_names list.\n    Note: the TOTAL_CHANGES headline uses variant-level counts from the\n    markdown summary; the per-snapshot detail intentionally shows the\n    simpler metric-name-level view.\n    \"\"\"\n    added_names = sorted(changes['added'].keys())\n    removed_names = sorted(changes['removed'].keys())\n    modified_names = sorted(changes['modified'].keys())\n\n    # Union of all changed metric names, deduplicated, sorted, and capped\n    # to avoid unbounded artifact growth. The publish workflow enforces a\n    # matching cap (MAX_METRIC_NAMES_PER_SNAPSHOT).\n    all_names = sorted(set(added_names) | set(removed_names) | set(modified_names))\n    capped = all_names[:MAX_METRIC_NAMES]\n\n    # Compute counts from the capped list so they match the displayed names.\n    added_set = set(added_names)\n    removed_set = set(removed_names)\n    modified_set = set(modified_names)\n\n    return {\n        'added': sum(1 for n in capped if n in added_set),\n        'removed': sum(1 for n in capped if n in removed_set),\n        'modified': sum(1 for n in capped if n in modified_set),\n        'metric_names': capped,\n    }\n\n\ndef main():\n    parser = argparse.ArgumentParser(description='Generate metrics diff summary')\n    parser.add_argument('--diff', required=True, help='Path to unified diff file')\n    parser.add_argument('--output', required=True, help='Output summary file path')\n    parser.add_argument('--json-output', default=None,\n                       help='Optional path to write structured JSON change data')\n\n    args = parser.parse_args()\n\n    changes, raw_diff_sections, exclusion_count = parse_diff_file(args.diff)\n    summary = generate_diff_summary(changes, raw_diff_sections, exclusion_count)\n\n    with open(args.output, 'w') as f:\n        f.write(summary)\n\n    if args.json_output:\n        structured = generate_structured_json(changes)\n        with open(args.json_output, 'w') as f:\n            json.dump(structured, f, indent=2)\n        print(f\"Structured JSON saved to {args.json_output}\")\n\n    print(f\"Generated diff summary with {len(changes['added'])} additions, \"\n          f\"{len(changes['removed'])} removals, \"\n          f\"{len(changes['modified'])} modifications and \"\n          f\"{exclusion_count} exclusions\")\n    print(f\"Summary saved to {args.output}\")\n\nif __name__ == '__main__':\n    main()"
  },
  {
    "path": "scripts/e2e/metrics_summary.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Enable debug tracing and exit on error\nset -exo pipefail\n\nMETRICS_DIR=\"${METRICS_DIR:-./.artifacts}\"\ndeclare -a summary_files=()\ndeclare -a json_files=()\ntotal_changes=0\n\necho \"Starting metrics diff processing in directory: $METRICS_DIR\"\necho \"Directory structure:\"\nls -la \"$METRICS_DIR\" || echo \"Metrics directory listing failed\"\n\n# Verify 1-to-1: every metrics_snapshot_* artifact must have a diff_metrics_snapshot_* artifact.\n# verify-metrics-snapshot always uploads a diff artifact on PRs (empty stub if no baseline),\n# so a missing diff dir means that action never ran for that snapshot — an infra failure.\necho \"=== Checking for missing diff artifacts ===\"\ndeclare -a missing_diffs=()\nfound_any_snapshot=false\nfor snapshot_dir in \"$METRICS_DIR\"/metrics_snapshot_*/; do\n    [ -d \"$snapshot_dir\" ] || continue\n    found_any_snapshot=true\n    name=$(basename \"$snapshot_dir\")\n    if [ ! -d \"$METRICS_DIR/diff_$name\" ]; then\n        echo \"::error::Missing diff artifact for snapshot: $name\"\n        missing_diffs+=(\"$name\")\n    else\n        echo \"OK: diff_$name present\"\n    fi\ndone\nif [ \"$found_any_snapshot\" = false ]; then\n    echo \"::error::No metrics_snapshot_* artifacts found; E2E jobs may not have run\"\n    missing_diffs+=(\"(no snapshot artifacts found)\")\nfi\nif [ ${#missing_diffs[@]} -gt 0 ]; then\n    echo \"INFRA_ERRORS=true\" >> \"$GITHUB_OUTPUT\"\nelse\n    echo \"INFRA_ERRORS=false\" >> \"$GITHUB_OUTPUT\"\nfi\n\n# Debug: List all diff files found\necho \"=== Searching for diff files ===\"\nfind \"$METRICS_DIR\" -type f -name \"diff_*.txt\" | while read -r file; do\n    echo \"Found diff file: $file\"\ndone\n\n# Process all non-empty diff files.\n# Empty diff files are stubs uploaded by verify-metrics-snapshot when there is no\n# baseline or when compare_metrics.py found no differences (it only writes to the\n# output file when differences exist). The 1-to-1 directory check above already\n# verified the action ran; here we only want to summarise actual changes.\nwhile IFS= read -r -d '' diff_file; do\n    if [ ! -s \"$diff_file\" ]; then\n        echo \"Skipping empty diff file (no changes or no baseline): $diff_file\"\n        continue\n    fi\n    echo \"Processing diff file: $diff_file\"\n\n    # Derive the unique snapshot name from the artifact directory (e.g.,\n    # diff_metrics_snapshot_cassandras_4.x_v004_v2_manual -> metrics_snapshot_cassandras_4.x_v004_v2_manual).\n    # Using the directory rather than the file name is necessary because all matrix\n    # variants of the same backend share an identical file name inside their artifact\n    # (e.g., diff_metrics_snapshot_cassandra.txt), while the artifact directory name\n    # is always unique (it includes major version, schema, jaeger-version, etc.).\n    dir=$(dirname \"$diff_file\")\n    snapshot_name=$(basename \"$dir\")\n    snapshot_name=${snapshot_name#diff_}\n\n    # Generate summary for this diff\n    summary_file=\"$dir/summary_$snapshot_name.md\"\n    json_file=\"$dir/changes_$snapshot_name.json\"\n\n    echo \"Generating summary for $snapshot_name\"\n    python3 ./scripts/e2e/metrics_summary.py \\\n        --diff \"$diff_file\" \\\n        --output \"$summary_file\" \\\n        --json-output \"$json_file\"\n\n    summary_files+=(\"$summary_file\")\n    json_files+=(\"$json_file\")\n    echo \"Generated summary at: $summary_file\"\ndone < <(find \"$METRICS_DIR\" -type f -name \"diff_*.txt\" -print0)\n\n# Output results\n# Calculate total changes across all files\ntotal_changes=0\n\nif [ ${#summary_files[@]} -eq 0 ]; then\n    echo \"No diff files found; all metrics are within baseline.\"\nelse\n    for summary_file in \"${summary_files[@]}\"; do\n        changes=$(grep -F \"**Total Changes:**\" \"$summary_file\" | awk '{print $3}')\n        total_changes=$((total_changes + changes))\n    done\nfi\n\necho \"Total changes across all snapshots: $total_changes\"\necho \"TOTAL_CHANGES=$total_changes\" >> \"$GITHUB_OUTPUT\"\n\nif [ ${#missing_diffs[@]} -gt 0 ]; then\n    echo \"CONCLUSION=failure\" >> \"$GITHUB_OUTPUT\"\nelif [ \"$total_changes\" -gt 0 ]; then\n    echo \"CONCLUSION=failure\" >> \"$GITHUB_OUTPUT\"\nelse\n    echo \"CONCLUSION=success\" >> \"$GITHUB_OUTPUT\"\nfi\n\n# Merge per-snapshot JSON files into a single metrics_snapshots.json.\n# Each entry gets a \"snapshot\" field with the snapshot name.\n# Capped at 50 entries to match the publish workflow's MAX_SNAPSHOTS limit.\n# The trusted publish workflow validates this data before rendering.\npython3 - \"$METRICS_DIR\" \"${json_files[@]}\" <<'PYEOF'\nimport json, os, sys\nmetrics_dir = sys.argv[1]\nMAX_SNAPSHOTS = 50\nsnapshots = []\nfor path in sys.argv[2:]:\n    if len(snapshots) >= MAX_SNAPSHOTS:\n        print(f\"Warning: capped at {MAX_SNAPSHOTS} snapshots\", file=sys.stderr)\n        break\n    try:\n        with open(path) as f:\n            data = json.load(f)\n        basename = os.path.basename(path)\n        name = basename.removeprefix('changes_').removesuffix('.json')\n        data['snapshot'] = name\n        snapshots.append(data)\n    except Exception as e:\n        print(f\"Warning: could not read {path}: {e}\", file=sys.stderr)\noutput_path = os.path.join(metrics_dir, 'metrics_snapshots.json')\nsnapshots.sort(key=lambda s: s.get('snapshot', ''))\nwith open(output_path, 'w') as f:\n    json.dump(snapshots, f, indent=2)\nprint(f\"Merged {len(snapshots)} snapshot(s) into {output_path}\")\nPYEOF\n\n# Log the combined summary to the console (visible in CI run logs).\n# Structured conclusions are already emitted to $GITHUB_OUTPUT above.\necho \"=== Metrics Comparison Summary ===\"\n\nif [ ${#missing_diffs[@]} -gt 0 ]; then\n    echo \"::error::Infrastructure error: diff artifacts missing for: ${missing_diffs[*]}\"\n    echo \"(These snapshots did not produce a diff artifact — the verify-metrics-snapshot action may not have run.)\"\nfi\n\nif [ \"$total_changes\" -gt 0 ]; then\n    echo \"::error::${total_changes} metric change(s) detected across all snapshots\"\n    echo \"\"\n    for summary_file in \"${summary_files[@]}\"; do\n        file_name=$(basename \"$summary_file\" .md)\n        echo \"--- ${file_name} ---\"\n        echo \"\"\n        cat \"$summary_file\"\n        echo \"\"\n    done\nelif [ ${#missing_diffs[@]} -gt 0 ]; then\n    echo \"No metric changes in available diffs, but some diff artifacts were missing (see above).\"\nelse\n    echo \"No metric changes detected.\"\nfi\n\necho \"Metrics diff processing completed\"\n"
  },
  {
    "path": "scripts/e2e/spm.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\n# Log function that adds timestamp to all messages\nlog() {\n  echo \"[$(date -u '+%Y-%m-%d %H:%M:%S')] $*\"\n}\n\nprint_help() {\n  log \"Usage: $0 [-m metricstore]\"\n  log \"-m: Which database to use as metrics store: 'prometheus' (default) or 'elasticsearch' or 'opensearch'\"\n  log \"-h: Print help\"\n  exit 1\n}\n\nMETRICSTORE='prometheus'\ncompose_file=docker-compose/monitor/docker-compose.yml\nmake_target=\"dev\"\n\nwhile getopts \"m:h\" opt; do\n  case \"${opt}\" in\n  m)\n    METRICSTORE=${OPTARG}\n    ;;\n  *)\n    print_help\n    ;;\n  esac\ndone\n\nset -x # Enable verbose logging for debugging\n\n# Validate metricstore option\ncase \"$METRICSTORE\" in\n  \"prometheus\"|\"elasticsearch\"|\"opensearch\")\n    # Valid options\n    ;;\n  *)\n    log \"❌ ERROR: Invalid metricstore option: $METRICSTORE\"\n    print_help\n    ;;\nesac\n\n# Set compose file based on metricstore\nif [ \"$METRICSTORE\" == \"elasticsearch\" ]; then\n  compose_file=docker-compose/monitor/docker-compose-elasticsearch.yml\n  make_target=\"elasticsearch\"\nfi\n\nif [ \"$METRICSTORE\" == \"opensearch\" ]; then\n  compose_file=docker-compose/monitor/docker-compose-opensearch.yml\n  make_target=\"opensearch\"\nfi\n\ntimeout=600\nend_time=$((SECONDS + timeout))\nsuccess=\"false\"\n\nexport SPANMETRICS_FLUSH_INTERVAL=1s # flush quickly to make IT run faster\n\ncheck_service_health() {\n  local service_name=$1\n  local url=$2\n  log \"Checking health of service: $service_name at $url\"\n\n  local wait_seconds=3\n  local curl_params=(\n    --silent\n    --output\n    /dev/null\n    --write-out\n    \"%{http_code}\"\n  )\n  while [ $SECONDS -lt $end_time ]; do\n    if [[ \"$(curl \"${curl_params[@]}\" \"${url}\")\" == \"200\" ]]; then\n      log \"✅ $service_name is healthy\"\n      return 0\n    fi\n    log \"Waiting for $service_name to be healthy...\"\n    sleep $wait_seconds\n  done\n\n  log \"❌ ERROR: $service_name did not become healthy in time\"\n  return 1\n}\n\n# Function to check if all services are healthy\nwait_for_services_to_be_healthy() {\n  log \"Waiting for services to be up and running...\"\n\n  case \"$METRICSTORE\" in\n    \"elasticsearch\")\n      check_service_health \"Elasticsearch\" \"http://localhost:9200\"\n      ;;\n    \"opensearch\")\n      check_service_health \"Opensearch\" \"http://localhost:9200\"\n      ;;\n    \"prometheus\")\n      check_service_health \"Prometheus\" \"http://localhost:9090/query\"\n      ;;\n  esac\n\n  check_service_health \"Jaeger\" \"http://localhost:16686\"\n}\n\nget_expected_operations_of_service() {\n  # Which span names do we expect from which service? \n  # See https://github.com/yurishkuro/microsim/blob/main/config/hotrod.go\n  local service=$1\n  case \"$service\" in\n    \"driver\")\n      echo \"/FindNearest\"\n      ;;\n    \"customer\")\n      echo \"/customer\"\n      ;;\n    \"mysql\")\n      echo \"/sql_select\"\n      ;;\n    \"redis\")\n      echo \"/FindDriverIDs /GetDriver\"\n      ;;\n    \"frontend\")\n      echo \"/dispatch\"\n      ;;\n    \"route\")\n      echo \"/GetShortestRoute\"\n      ;;\n    \"ui\")\n      echo \"/\"\n      ;;\n    *)\n      echo \"\"\n      ;;\n  esac\n}\n\n# Validate that found operations match expected operations for a service\nvalidate_operations_for_service() {\n  local service=$1\n  local found_operations=$2\n  \n  local expected_operations\n  expected_operations=$(get_expected_operations_of_service \"$service\")\n  \n  # If no expected operations defined for this service, skip validation\n  if [[ -z \"$expected_operations\" ]]; then\n    return 0\n  fi\n  \n  # Log expected and found operations\n  if [[ -n \"$found_operations\" ]]; then\n    echo \"Expected operations for service '$service': [$expected_operations] | Found operations: [$found_operations]\"\n  else\n    echo \"Expected operations for service '$service': [$expected_operations] | Found operations: []\"\n  fi\n  \n  # If no operations found, that's an error\n  if [[ -z \"$found_operations\" ]]; then\n    echo \"❌ ERROR: No operations found for service '$service', but expected: [$expected_operations]\"\n    return 1\n  fi\n  \n  # Parse comma-separated operations (format: \"op1, op2, op3\")\n  # Convert to space-separated and normalize whitespace\n  local found_ops_list\n  found_ops_list=$(echo \"$found_operations\" | sed 's/,/ /g' | tr -s ' ' | sed 's/^ *//;s/ *$//')\n  \n  # Check each found operation against expected ones\n  local found_op\n  for found_op in $found_ops_list; do\n    # Remove any leading/trailing spaces\n    found_op=$(echo \"$found_op\" | sed 's/^ *//;s/ *$//')\n    \n    # Skip empty operations\n    if [[ -z \"$found_op\" ]]; then\n      continue\n    fi\n    \n    # Check if this operation is in the expected list\n    local is_expected=false\n    local expected_op\n    for expected_op in $expected_operations; do\n      if [[ \"$found_op\" == \"$expected_op\" ]]; then\n        is_expected=true\n        break\n      fi\n    done\n    \n    if [[ \"$is_expected\" == \"false\" ]]; then\n      echo \"❌ ERROR: Unexpected operation '$found_op' found for service '$service'. Expected operations: [$expected_operations]\"\n      return 1\n    fi\n  done\n  \n  echo \"✅ Operation validation passed for service '$service'\"\n  return 0\n}\n\ncurl_metrics() {\n  local endpoint=$1\n  local service=$2\n  local extra_query=${3:-}\n  # Time constants in milliseconds\n  local fiveMinutes=300000\n  local oneMinute=60000\n  local tenSeconds=10000\n\n  # When endTs=(blank) the server will default it to now().\n  local url=\"http://localhost:16686/api/metrics/${endpoint}?service=${service}&endTs=&lookback=${fiveMinutes}&step=${tenSeconds}&ratePer=${oneMinute}\"\n  if [[ -n \"$extra_query\" ]]; then\n    url=\"${url}&${extra_query}\"\n  fi\n\n  curl -s \"$url\"\n}\n\n# Function to validate the service metrics\nvalidate_service_metrics() {\n    local service=$1\n    response=$(curl_metrics \"calls\" \"$service\")\n    if ! assert_service_name_equals \"$response\" \"$service\" ; then\n      return 1\n    fi\n\n    # Check that we receive some non-zero metric values from this service\n    local non_zero_count\n    non_zero_count=$(count_non_zero_metrics_point \"$response\")\n    local desired_non_zero_count\n    desired_non_zero_count=4\n    log \"Metrics data points found (non-zero): ${non_zero_count}\"\n\n    if [[ $non_zero_count -lt $desired_non_zero_count ]]; then\n      echo \"⏳ Want to see at least $desired_non_zero_count non-zero data points\"\n      return 1\n    fi\n\n    # Validate if labels are correct\n    response=$(curl_metrics \"calls\" \"$service\" \"groupByOperation=true\")\n    if ! assert_labels_set_equals \"$response\" \"operation service_name\" ; then\n      return 1\n    fi\n    \n    # Validate operations from this service are what we expect.\n    echo \"Checking operations for service: $service\"\n    local operations\n    operations=$(extract_operations \"$response\")\n    if [[ -n \"$operations\" ]]; then\n      # Validate that found operations match expected ones\n      if ! validate_operations_for_service \"$service\" \"$operations\" \"calls\"; then\n        return 1\n      fi\n    else\n      echo \"❌ ERROR No operations found yet for service '${service}'. We expected to find some.\"\n    fi\n\n    ### Validate Errors Rate metrics\n    response=$(curl_metrics \"errors\" \"$service\")\n    if ! assert_service_name_equals \"$response\" \"$service\" ; then\n      return 1\n    fi\n\n    response=$(curl_metrics \"errors\" \"$service\" \"groupByOperation=true\")\n    if ! assert_labels_set_equals \"$response\" \"operation service_name\" ; then\n      return 1\n    fi\n\n    non_zero_count=$(count_non_zero_metrics_point \"$response\")\n    local services_with_error=\"driver frontend ui redis\"\n    if [[ \"$services_with_error\" =~ $service ]]; then # the service is in the list\n      if [[ $non_zero_count == \"0\" ]]; then\n        log \"⏳ ERROR: expect service $service to have positive errors rate. You may have to wait for an error span to be created because microsim generates errors probabilistically: https://github.com/yurishkuro/microsim/blob/d532cf986675389494c11254ea3ae12c4297e94f/config/hotrod.go#L116\"\n        return 1\n      fi\n    else\n      if [[ $non_zero_count != \"0\" ]]; then\n        log \"❌ ERROR: expect service $service to have 0 errors, but have $non_zero_count data points with positive errors\"\n        return 1\n      fi\n    fi\n\n\n    return 0\n}\n\nassert_service_name_equals() {\n  local response=$1\n  local expected=$2\n  # First check if metrics structure exists at all\n  if ! echo \"$response\" | jq -e '.metrics and .metrics[0]' >/dev/null; then\n    log \"⏳ Metrics not available yet (no metrics array)\"\n    return 1\n  fi\n  service_name=$(echo \"$response\" | jq -r 'if .metrics and .metrics[0] then .metrics[0].labels[] | select(.name==\"service_name\") | .value else empty end')\n  if [[ \"$service_name\" != \"$expected\" ]]; then\n    log \"❌ ERROR: Obtained service_name: '$service_name' are not same as expected: '$expected'\"\n    return 1\n  fi\n  return 0\n}\n\nassert_labels_set_equals() {\n  local response=$1\n  local expected=\"$2 \" # need one extra space due to how labels is computed\n\n  labels=$(echo \"$response\" | jq -r '.metrics[0].labels[].name' | sort | tr '\\n' ' ')\n\n  if [[ \"$labels\" != \"$expected\" ]]; then\n    log \"❌ ERROR: Obtained labels: '$labels' are not same as expected labels: '$expected'\"\n    return 1\n  fi\n  return 0\n}\n\nextract_operations() {\n  local response=$1\n  # Extract all unique operation names from all metrics in the response\n  # Each metric has labels array, and when groupByOperation=true, each metric has a label with name==\"operation\"\n  local operations\n  operations=$(echo \"$response\" | jq -r '\n    if .metrics and (.metrics | length > 0) then\n      [.metrics[] | .labels[] | select(.name==\"operation\") | .value] | unique | sort | .[]\n    else\n      empty\n    end' 2>/dev/null)\n  \n  if [[ -z \"$operations\" ]]; then\n    echo \"\"\n    return 0\n  fi\n  \n  # Return operations as a comma-separated list\n  echo \"$operations\" | tr '\\n' ',' | sed 's/,$//' | sed 's/,/, /g'\n}\n\ncount_non_zero_metrics_point() {\n  echo \"$1\" | jq -r '[.metrics[0].metricPoints[].gaugeValue.doubleValue | select(. != 0 and (. | tostring != \"NaN\"))] | length'\n}\n\ncheck_spm() {\n  local wait_seconds=10\n  local successful_service=0\n  services_list=(\"driver\" \"customer\" \"mysql\" \"redis\" \"frontend\" \"route\" \"ui\")\n  for service in \"${services_list[@]}\"; do\n    log \"Processing service: $service\"\n    while [ $SECONDS -lt $end_time ]; do\n      if validate_service_metrics \"$service\"; then\n        log \"✅ Found all expected metrics for service '$service'\"\n        successful_service=$((successful_service + 1))\n        break\n      fi\n      sleep $wait_seconds\n    done\n  done\n  if [ $successful_service -lt ${#services_list[@]} ]; then\n    log \"❌ ERROR: Expected metrics from ${#services_list[@]} services, found only ${successful_service}\"\n    exit 1\n  else\n    log \"✅ All service have valid metrics\"\n  fi\n}\n\ndump_logs() {\n  log \"::group:: docker logs\"\n  docker compose -f $compose_file logs\n  log \"::endgroup::\"\n}\n\nteardown_services() {\n  if [[ \"$success\" == \"false\" ]]; then\n    dump_logs\n  fi\n  docker compose -f $compose_file down\n}\n\nmain() {\n  (cd docker-compose/monitor && make build BINARY=\"jaeger\" && make $make_target DOCKER_COMPOSE_ARGS=\"-d\")\n\n  wait_for_services_to_be_healthy\n  check_spm\n  success=\"true\"\n}\n\ntrap teardown_services EXIT INT\n\nmain"
  },
  {
    "path": "scripts/lint/check-go-version.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nversion_regex='[0-9]\\.[0-9][0-9]'\nupdate=false\nverbose=false\n\nwhile getopts \"uvdx\" opt; do\n    case $opt in\n        u) update=true ;;\n        v) verbose=true ;;\n        x) set -x ;;\n        *) echo \"Usage: $0 [-u] [-v] [-d]\" >&2\n           exit 1\n           ;;\n    esac\ndone\n\n# Fetch latest go release version\n# go_latest_version=$(curl -s https://go.dev/dl/?mode=json | jq -r '.[0].version' | awk -F'.' '{gsub(\"go\", \"\"); print $1\".\"$2}')\n#\n# UPDATE: we don't use the logic above because it causes CI to fail when new version of Go is released,\n# which may create circular dependencies when other utilities need to be upgraded. Instead use the go\n# version declared in the main go.mod. Updates to that version will be handled by the bots.\ngo_latest_version=$(grep \"^go \" go.mod | sed 's/^go \\([0-9]\\.[0-9]*\\).*/\\1/')\n\nfiles_to_update=0\n\nfunction update() {\n    local file=$1\n    local pattern=$2\n    local current=$3\n    local target=$4\n\n    newfile=$(mktemp)\n    old_IFS=$IFS\n    IFS=''\n    while read -r line; do\n        match=$(echo \"$line\" | grep -e \"$pattern\")\n        if [[ \"$match\" != \"\" ]]; then\n            line=${line//${current}/${target}}\n        fi\n        echo \"$line\" >> \"$newfile\"\n    done < \"$file\"\n    IFS=$old_IFS\n\n    if [ $verbose = true ]; then\n        diff \"$file\" \"$newfile\"\n    fi\n\n    mv \"$newfile\" \"$file\"\n}\n\nfunction check() {\n    local file=$1\n    local pattern=$2\n    local target=$3\n\n    go_version=$(grep -e \"$pattern\" \"$file\" | head -1 | sed \"s/^.*\\($version_regex\\).*$/\\1/\")\n\n    if [ \"$go_version\" = \"$target\" ]; then\n        mismatch=''\n    else\n        mismatch=\"*** needs update to $target ***\"\n        files_to_update=$((files_to_update+1))\n    fi\n\n    if [[ $update = true && \"$mismatch\" != \"\" ]]; then\n        # Detect if the line includes a patch version\n        if [[ \"$go_version\" =~ $version_regex\\.[0-9]+ ]]; then\n            echo \"Patch version detected in $file. Manual update required.\"\n            exit 1\n        fi\n        update \"$file\" \"$pattern\" \"$go_version\" \"$target\"\n        mismatch=\"*** => $target ***\"\n    fi\n\n    printf \"%-50s Go version: %s %s\\n\" \"$file\" \"$go_version\" \"$mismatch\"\n}\n\n# In the main go.mod file (and linter config) we want the same Go version N.\n# All importable code has been moved to internal packages, so there's no need\n# to maintain backward compatibility with older compilers.\ncheck go.mod \"^go\\s\\+$version_regex\" \"$go_latest_version\"\ncheck .golangci.yml \"go:\\s\\+\\\"$version_regex\\\"\" \"$go_latest_version\"\n\n# find all other go.mod files in the repository and check for latest Go version\nfor file in $(find . -type f -name go.mod | grep -v '^./go.mod'); do\n    if [[ $file == \"./idl/go.mod\" ]]; then\n        continue\n    fi\n    if [[ $file == \"./idl/internal/tools/go.mod\" ]]; then\n        continue\n    fi\n    check \"$file\" \"^go\\s\\+$version_regex\" \"$go_latest_version\"\ndone\n\nIFS='|' read -r -a gha_workflows <<< \"$(grep -rl go-version .github/workflows | tr '\\n' '|')\"\nfor gha_workflow in \"${gha_workflows[@]}\"; do\n    check \"$gha_workflow\" \"^\\s*go-version:\\s\\+$version_regex\" \"$go_latest_version\"\ndone\n\nif [ $files_to_update -eq 0 ]; then\n    echo \"All files are up to date.\"\nelse\n    if [[ $update = true ]]; then\n        echo \"$files_to_update file(s) updated.\"\n    else\n        echo \"$files_to_update file(s) must be updated. Rerun this script with -u argument.\"\n        exit 1\n    fi\nfi\n"
  },
  {
    "path": "scripts/lint/check-goleak-files.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euo pipefail\n\nbad_pkgs=0\ntotal_pkgs=0\nfailed_pkgs=0\ninvalid_use_pkgs=0\n\n# shellcheck disable=SC2048\nfor dir in $*; do\n  ((total_pkgs+=1))\n  if [[ -f \"${dir}/.nocover\" ]]; then\n    continue\n  fi\n  testFiles=$(find \"${dir}\" -maxdepth 1 -name '*_test.go')\n  if [[ -z \"$testFiles\" ]]; then\n    continue\n  fi\n  good=0\n  invalid=0\n  for test in ${testFiles}; do\n    if grep -q \"TestMain\" \"${test}\" && grep -q \"testutils.VerifyGoLeaks\" \"${test}\"; then\n      if [ \"${dir}\" != \"./internal/storage/integration/\" ] &&  grep -q \"testutils.VerifyGoLeaksForES\" \"${test}\"; then\n          invalid=1\n          break\n      fi\n      good=1\n      break\n    fi\n  done\n\n  if ((good == 0)); then\n    if ((invalid == 1)); then\n      echo \"Error(check-goleak): VerifyGoLeaksForES should only be used in integration package but it is used in ${dir} also\"\n      ((invalid_use_pkgs+=1))\n    else\n      echo \"Error(check-goleak): no goleak check in package ${dir}\"\n      ((bad_pkgs+=1))\n      ((failed_pkgs+=1))\n    fi\n  fi\ndone\n\nfunction help() {\n  echo \"\tSee pkg/version/package_test.go as example of adding the checks.\"\n}\n\nif ((failed_pkgs > 0)); then\n  echo \"⛔ Fatal(check-goleak): no goleak check in ${bad_pkgs} package(s), ${failed_pkgs} of which not allowed.\"\n  help\n  exit 1\nelif ((invalid_use_pkgs > 0)); then\n  echo \"⛔ Fatal(check-goleak): use of VerifyGoLeaksForES in package(s) ${invalid_use_pkgs} which is not allowed\"\n  help\n  exit 1\nelif ((bad_pkgs > 0)); then\n  echo \"🐞 Warning(check-goleak): no goleak check in ${bad_pkgs} package(s).\"\n  help\nelse\n  echo \"✅ Info(check-goleak): no issues after scanning ${total_pkgs} package(s).\"\nfi\n"
  },
  {
    "path": "scripts/lint/check-jaeger-idl-version.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euo pipefail\n\ndependency=\"github.com/jaegertracing/jaeger-idl\"\n\nget_gomod_version() {\n  gomod_dep=$(grep $dependency <go.mod)\n  if [ ! \"$gomod_dep\" ]; then\n    printf \"Error: jaeger-idl dependency not found in go mod\\n\" >&2\n    exit 1\n  fi\n  gomod_version=$(echo \"$gomod_dep\" | awk '{print $2}')\n  echo \"$gomod_version\"\n}\n\nget_submodule_version() {\n  cd idl\n  git fetch --tags\n  commit_version=$(git rev-parse HEAD)\n  semver=$(git describe --tags --exact-match \"$commit_version\")\n  if [ ! \"$semver\" ]; then\n    printf \"Error: failed getting version from submodule\\n\" >&2\n    exit 1\n  fi\n  echo \"$semver\"\n}\n\ngomod_semver=$(get_gomod_version) || exit 1\nsubmod_semver=$(get_submodule_version) || exit 1\nif [[ \"$gomod_semver\" != \"$submod_semver\" ]]; then\n  printf \"Error: jaeger-idl version mismatch: go.mod %s != submodule %s\\n\" \"$gomod_semver\" \"$submod_semver\" >&2\n  exit 1\nfi\necho \"jaeger-idl version match: OK\"\n"
  },
  {
    "path": "scripts/lint/check-semconv-version.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\npackage_name=\"go.opentelemetry.io/otel/semconv\"\nversion_regex=\"v[0-9]\\.[0-9]\\+\\.[0-9]\\+\"\n\nfunction find_files() {\n    find . -type f -name \"*.go\" -exec grep -o -H \"$package_name/$version_regex\" {} + \\\n    | tr ':' ' ' \\\n    | sed \"s|$package_name/||g\"\n}\ncount=$(find_files | awk '{print $2}' | sort -u | wc -l)\n\nif [ \"$count\" -gt 1 ]; then\n    printf \"%-70s | %s\\n\" \"Source File\" \"Semconv Version\"\n    printf \"%-70s | %s\\n\" \"================\" \"================\"\n    while IFS=' ' read -r file_name version; do\n        printf \"%-70s | %s\\n\" \"$file_name\" \"$version\"\n    done < <(find_files)\n    printf \"Error: %d different semconv versions detected.\\n\" \"$count\"\n    echo \"Run ./scripts/lint/update-semconv-version.sh to update semconv to latest version.\"\n    exit 1\nfi\n"
  },
  {
    "path": "scripts/lint/check-test-files.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# This script checks that all directories with go files\n# have at least one *_test.go file or a .nocover file.\n\nset -euo pipefail\n\nNO_TEST_FILE_DIRS=\"\"\ntotal_pkgs=0\n\n# shellcheck disable=SC2048\nfor dir in $*; do\n  ((total_pkgs+=1))\n  mainFile=$(find \"${dir}\" -maxdepth 1 -name 'main.go')\n  testFiles=$(find \"${dir}\" -maxdepth 1 -name '*_test.go')\n  if [ -z \"${testFiles}\" ]; then\n    if [ -n \"${mainFile}\" ]; then\n      continue # single main does not require tests\n    fi\n    if [ -e \"${dir}/.nocover\" ]; then\n      reason=$(cat \"${dir}/.nocover\")\n      if [ \"${reason}\" == \"\" ]; then\n        echo \"error: ${dir}/.nocover must specify reason\" >&2\n        exit 1\n      fi\n      echo \"Package excluded from coverage: ${dir}\"\n      echo \"  reason: ${reason}\" | sed \"s/FIXME/🔴 FIXME/\"\n      continue\n    fi\n    if [ -z \"${NO_TEST_FILE_DIRS}\" ]; then\n      NO_TEST_FILE_DIRS=\"${dir}\"\n    else\n      NO_TEST_FILE_DIRS=\"${NO_TEST_FILE_DIRS} ${dir}\"\n    fi\n  fi\ndone\n\nif [ -n \"${NO_TEST_FILE_DIRS}\" ]; then\n  echo \"*** directories without *_test.go files:\" >&2\n  echo \"${NO_TEST_FILE_DIRS}\" | tr ' ' '\\n' >&2\n  echo \"error: at least one *_test.go file must be in all directories with go files so that they are counted for code coverage\" >&2\n  echo \"       if no tests are possible for a package (e.g. it only defines types), create empty_test.go\" >&2\n  exit 1\nelse\n  echo \"✅ Info(check-test-files): no issues after scanning ${total_pkgs} package(s).\"\nfi\n"
  },
  {
    "path": "scripts/lint/dco_check.py",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Script copied from https://github.com/christophebedard/dco-check/blob/master/dco_check/dco_check.py\n#\n# Copyright 2020 Christophe Bedard\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Check that all commits for a proposed change are signed off.\"\"\"\n\nimport argparse\nfrom collections import defaultdict\nimport json\nimport os\nimport re\nimport subprocess\nimport sys\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Optional\nfrom typing import Tuple\nfrom urllib import request\n\n\n__version__ = '0.4.0'\n\n\nDEFAULT_BRANCH = 'master'\nDEFAULT_REMOTE = 'origin'\nENV_VAR_CHECK_MERGE_COMMITS = 'DCO_CHECK_CHECK_MERGE_COMMITS'\nENV_VAR_DEFAULT_BRANCH = 'DCO_CHECK_DEFAULT_BRANCH'\nENV_VAR_DEFAULT_BRANCH_FROM_REMOTE = 'DCO_CHECK_DEFAULT_BRANCH_FROM_REMOTE'\nENV_VAR_DEFAULT_REMOTE = 'DCO_CHECK_DEFAULT_REMOTE'\nENV_VAR_EXCLUDE_EMAILS = 'DCO_CHECK_EXCLUDE_EMAILS'\nENV_VAR_EXCLUDE_PATTERN = 'DCO_CHECK_EXCLUDE_PATTERN'\nENV_VAR_QUIET = 'DCO_CHECK_QUIET'\nENV_VAR_VERBOSE = 'DCO_CHECK_VERBOSE'\nTRAILER_KEY_SIGNED_OFF_BY = 'Signed-off-by:'\n\n\nclass EnvDefaultOption(argparse.Action):\n    \"\"\"\n    Action that uses an env var value as the default if it exists.\n\n    Inspired by: https://stackoverflow.com/a/10551190/6476709\n    \"\"\"\n\n    def __init__(\n        self,\n        env_var: str,\n        default: Any,\n        help: Optional[str] = None,  # noqa: A002\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Create an EnvDefaultOption.\"\"\"\n        # Set default to env var value if it exists\n        if env_var in os.environ:\n            default = os.environ[env_var]\n        if help:  # pragma: no cover\n            help += f' [env: {env_var}]'\n        super(EnvDefaultOption, self).__init__(\n            default=default,\n            help=help,\n            **kwargs,\n        )\n\n    def __call__(  # noqa: D102\n        self,\n        parser: argparse.ArgumentParser,\n        namespace: argparse.Namespace,\n        values: Any,\n        option_string: Optional[str] = None,\n    ) -> None:\n        setattr(namespace, self.dest, values)\n\n\nclass EnvDefaultStoreTrue(argparse.Action):\n    \"\"\"\n    Action similar to 'store_true' that uses an env var value as the default if it exists.\n\n    Partly copied from arparse.{_StoreConstAction,_StoreTrueAction}.\n    \"\"\"\n\n    def __init__(\n        self,\n        option_strings: str,\n        dest: str,\n        env_var: str,\n        default: bool = False,\n        help: Optional[str] = None,  # noqa: A002\n    ) -> None:\n        \"\"\"Create an EnvDefaultStoreTrue.\"\"\"\n        # Set default value to true if the env var exists\n        default = env_var in os.environ\n        if help:  # pragma: no cover\n            help += f' [env: {env_var} (any value to enable)]'\n        super(EnvDefaultStoreTrue, self).__init__(\n            option_strings=option_strings,\n            dest=dest,\n            nargs=0,\n            const=True,\n            default=default,\n            required=False,\n            help=help,\n        )\n\n    def __call__(  # noqa: D102\n        self,\n        parser: argparse.ArgumentParser,\n        namespace: argparse.Namespace,\n        values: Any,\n        option_string: Optional[str] = None,\n    ) -> None:\n        setattr(namespace, self.dest, self.const)\n\n\ndef get_parser() -> argparse.ArgumentParser:\n    \"\"\"Get argument parser.\"\"\"\n    parser = argparse.ArgumentParser(\n        description='Check that all commits of a proposed change have a DCO, i.e. are signed-off.',\n    )\n    default_branch_group = parser.add_mutually_exclusive_group()\n    default_branch_group.add_argument(\n        '-b', '--default-branch', metavar='BRANCH',\n        action=EnvDefaultOption, env_var=ENV_VAR_DEFAULT_BRANCH,\n        default=DEFAULT_BRANCH,\n        help=(\n            'default branch to use, if necessary (default: %(default)s)'\n        ),\n    )\n    default_branch_group.add_argument(\n        '--default-branch-from-remote',\n        action=EnvDefaultStoreTrue, env_var=ENV_VAR_DEFAULT_BRANCH_FROM_REMOTE,\n        default=False,\n        help=(\n            'get the default branch value from the remote (default: %(default)s)'\n        ),\n    )\n    parser.add_argument(\n        '-m', '--check-merge-commits',\n        action=EnvDefaultStoreTrue, env_var=ENV_VAR_CHECK_MERGE_COMMITS,\n        default=False,\n        help=(\n            'check sign-offs on merge commits as well (default: %(default)s)'\n        ),\n    )\n    parser.add_argument(\n        '-r', '--default-remote', metavar='REMOTE',\n        action=EnvDefaultOption, env_var=ENV_VAR_DEFAULT_REMOTE,\n        default=DEFAULT_REMOTE,\n        help=(\n            'default remote to use, if necessary (default: %(default)s)'\n        ),\n    )\n    parser.add_argument(\n        '-e', '--exclude-emails', metavar='EMAIL[,EMAIL]',\n        action=EnvDefaultOption, env_var=ENV_VAR_EXCLUDE_EMAILS,\n        default=None,\n        help=(\n            'exclude a comma-separated list of author emails from checks '\n            '(commits with an author email matching one of these emails will be ignored)'\n        ),\n    )\n    parser.add_argument(\n        '-p', '--exclude-pattern', metavar='REGEX',\n        action=EnvDefaultOption, env_var=ENV_VAR_EXCLUDE_PATTERN,\n        default=None,\n        help=(\n            'exclude regular expresssion matched author emails from checks '\n            '(commits with an author email matching regular expression pattern will be ignored)'\n        ),\n    )\n    output_options_group = parser.add_mutually_exclusive_group()\n    output_options_group.add_argument(\n        '-q', '--quiet',\n        action=EnvDefaultStoreTrue, env_var=ENV_VAR_QUIET,\n        default=False,\n        help=(\n            'quiet mode (do not print anything; simply exit with 0 or non-zero) '\n            '(default: %(default)s)'\n        ),\n    )\n    output_options_group.add_argument(\n        '-v', '--verbose',\n        action=EnvDefaultStoreTrue, env_var=ENV_VAR_VERBOSE,\n        default=False,\n        help=(\n            'verbose mode (print out more information) (default: %(default)s)'\n        ),\n    )\n    parser.add_argument(\n        '--version',\n        action='version',\n        help='show version number and exit',\n        version=f'dco-check version {__version__}',\n    )\n    return parser\n\n\ndef parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:\n    \"\"\"\n    Parse arguments.\n\n    :param argv: the arguments to use, or `None` for sys.argv\n    :return: the parsed arguments\n    \"\"\"\n    return get_parser().parse_args(argv)\n\n\nclass Options:\n    \"\"\"Simple container and utilities for options.\"\"\"\n\n    def __init__(self, parser: argparse.ArgumentParser) -> None:\n        \"\"\"Create using default argument values.\"\"\"\n        self.check_merge_commits = parser.get_default('m')\n        self.default_branch = parser.get_default('b')\n        self.default_branch_from_remote = parser.get_default('default-branch-from-remote')\n        self.default_remote = parser.get_default('r')\n        self.exclude_emails = parser.get_default('e')\n        self.exclude_pattern = parser.get_default('p')\n        self.quiet = parser.get_default('q')\n        self.verbose = parser.get_default('v')\n\n    def set_options(self, args: argparse.Namespace) -> None:\n        \"\"\"Set options using parsed arguments.\"\"\"\n        self.check_merge_commits = args.check_merge_commits\n        self.default_branch = args.default_branch\n        self.default_branch_from_remote = args.default_branch_from_remote\n        self.default_remote = args.default_remote\n        # Split into list and filter out empty elements\n        self.exclude_emails = list(filter(None, (args.exclude_emails or '').split(',')))\n        self.exclude_pattern = (\n            None if not args.exclude_pattern else re.compile(args.exclude_pattern)\n        )\n        self.quiet = args.quiet\n        self.verbose = args.verbose\n        # Shouldn't happen with a mutually exclusive group,\n        # but can happen if one is set with an env var\n        # and the other is set with an arg\n        if self.quiet and self.verbose:\n            # Similar message to what is printed when using args for both\n            get_parser().print_usage()\n            print(\"options '--quiet' and '--verbose' cannot both be true\")\n            sys.exit(1)\n        if self.default_branch != DEFAULT_BRANCH and self.default_branch_from_remote:\n            # Similar message to what is printed when using args for both\n            get_parser().print_usage()\n            print(\n                \"options '--default-branch' and '--default-branch-from-remote' cannot both be set\"\n            )\n            sys.exit(1)\n\n    def get_options(self) -> Dict[str, Any]:\n        \"\"\"Get all options as a dict.\"\"\"\n        return self.__dict__\n\n\noptions = Options(get_parser())\n\n\nclass Logger:\n    \"\"\"Simple logger to stdout which can be quiet or verbose.\"\"\"\n\n    def __init__(self, parser: argparse.ArgumentParser) -> None:\n        \"\"\"Create using default argument values.\"\"\"\n        self.__quiet = parser.get_default('q')\n        self.__verbose = parser.get_default('v')\n\n    def set_options(self, options: Options) -> None:\n        \"\"\"Set options using options object.\"\"\"\n        self.__quiet = options.quiet\n        self.__verbose = options.verbose\n\n    def print(self, msg: str = '', *args: Any, **kwargs: Any) -> None:  # noqa: A003\n        \"\"\"Print if not quiet.\"\"\"\n        if not self.__quiet:\n            print(msg, *args, **kwargs)\n\n    def verbose_print(self, msg: str = '', *args: Any, **kwargs: Any) -> None:\n        \"\"\"Print if verbose.\"\"\"\n        if self.__verbose:\n            print(msg, *args, **kwargs)\n\n\nlogger = Logger(get_parser())\n\n\ndef run(\n    command: List[str],\n) -> Optional[str]:\n    \"\"\"\n    Run command.\n\n    :param command: the command list\n    :return: the stdout output if the return code is 0, otherwise `None`\n    \"\"\"\n    output = None\n    try:\n        env = os.environ.copy()\n        if 'LANG' in env:\n            del env['LANG']\n        for key in list(env.keys()):\n            if key.startswith('LC_'):\n                del env[key]\n        process = subprocess.Popen(\n            command,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            env=env,\n        )\n        output_stdout, _ = process.communicate()\n        if process.returncode != 0:\n            logger.print(f'error: {output_stdout.decode(\"utf8\")}')\n        else:\n            output = output_stdout.rstrip().decode('utf8').strip('\\n')\n    except subprocess.CalledProcessError as e:\n        logger.print(f'error: {e.output.decode(\"utf8\")}')\n    return output\n\n\ndef is_valid_email(\n    email: str,\n) -> bool:\n    \"\"\"\n    Check if email is valid.\n\n    Simple regex checking for:\n        <nonwhitespace string>@<nonwhitespace string>.<nonwhitespace string>\n\n    :param email: the email address to check\n    :return: true if email is valid, false otherwise\n    \"\"\"\n    return bool(re.match(r'^\\S+@\\S+\\.\\S+', email))\n\n\ndef get_head_commit_hash() -> Optional[str]:\n    \"\"\"\n    Get the hash of the HEAD commit.\n\n    :return: the hash of the HEAD commit, or `None` if it failed\n    \"\"\"\n    command = [\n        'git',\n        'rev-parse',\n        '--verify',\n        'HEAD',\n    ]\n    return run(command)\n\n\ndef get_common_ancestor_commit_hash(\n    base_ref: str,\n) -> Optional[str]:\n    \"\"\"\n    Get the common ancestor commit of the current commit and a given reference.\n\n    See: git merge-base --fork-point\n\n    :param base_ref: the other reference\n    :return: the common ancestor commit, or `None` if it failed\n    \"\"\"\n    command = [\n        'git',\n        'merge-base',\n        '--fork-point',\n        base_ref,\n    ]\n    return run(command)\n\n\ndef fetch_branch(\n    branch: str,\n    remote: str = 'origin',\n) -> int:\n    \"\"\"\n    Fetch branch from remote.\n\n    See: git fetch\n\n    :param branch: the name of the branch\n    :param remote: the name of the remote\n    :return: zero for success, nonzero otherwise\n    \"\"\"\n    command = [\n        'git',\n        'fetch',\n        remote,\n        branch,\n    ]\n    # We don't want the output\n    return 0 if run(command) is not None else 1\n\n\ndef get_default_branch_from_remote(\n    remote: str,\n) -> Optional[str]:\n    \"\"\"\n    Get default branch from remote.\n\n    :param remote: the remote name\n    :return: the default branch, or None if it failed\n    \"\"\"\n    # https://stackoverflow.com/questions/28666357/git-how-to-get-default-branch#comment92366240_50056710  # noqa: E501\n    #   $ git remote show origin\n    cmd = ['git', 'remote', 'show', remote]\n    result = run(cmd)\n    if not result:\n        return None\n    result_lines = result.split('\\n')\n    branch = None\n    for result_line in result_lines:\n        # There is a two-space indentation\n        match = re.match('  HEAD branch: (.*)', result_line)\n        if match:\n            branch = match[1]\n            break\n    return branch\n\n\ndef get_commits_data(\n    base: str,\n    head: str,\n    ignore_merge_commits: bool = True,\n) -> Optional[str]:\n    \"\"\"\n    Get data (full sha & commit body) for commits in a range.\n\n    The range excludes the 'before' commit, e.g. ]base, head]\n    The output data contains data for individual commits, separated by special characters:\n       * 1st line: full commit sha\n       * 2nd line: author name and email\n       * 3rd line: commit title (subject)\n       * subsequent lines: commit body (which excludes the commit title line)\n       * record separator (0x1e)\n\n    :param base: the sha of the commit just before the start of the range\n    :param head: the sha of the last commit of the range\n    :param ignore_merge_commits: whether to ignore merge commits\n    :return: the data, or `None` if it failed\n    \"\"\"\n    command = [\n        'git',\n        'log',\n        f'{base}..{head}',\n        '--pretty=%H%n%an <%ae>%n%s%n%-b%x1e',\n    ]\n    if ignore_merge_commits:\n        command += ['--no-merges']\n    return run(command)\n\n\ndef split_commits_data(\n    commits_data: str,\n    commits_sep: str = '\\x1e',\n) -> List[str]:\n    \"\"\"\n    Split data into individual commits using a separator.\n\n    :param commits_data: the full data to be split\n    :param commits_sep: the string which separates individual commits\n    :return: the list of data for each individual commit\n    \"\"\"\n    # Remove leading/trailing newlines\n    commits_data = commits_data.strip('\\n')\n    # Split in individual commits and remove leading/trailing newlines\n    individual_commits = [\n        single_output.strip('\\n') for single_output in commits_data.split(commits_sep)\n    ]\n    # Filter out empty elements\n    individual_commits = list(filter(None, individual_commits))\n    return individual_commits\n\n\ndef extract_name_and_email(\n    name_and_email: str,\n) -> Optional[Tuple[str, str]]:\n    \"\"\"\n    Extract a name and an email from a 'name <email>' string.\n\n    :param name_and_email: the name and email string\n    :return: the extracted (name, email) tuple, or `None` if it failed\n    \"\"\"\n    match = re.search('(.*) <(.*)>', name_and_email)\n    if not match:\n        return None\n    return match.group(1), match.group(2)\n\n\ndef format_name_and_email(\n    name: Optional[str],\n    email: Optional[str],\n) -> str:\n    \"\"\"\n    Format a name and a email into a 'name <email>' string.\n\n    :param name: the name, or `None` if N/A\n    :param email: the email, or `None` if N/A\n    :return: the formatted string\n    \"\"\"\n    return f\"{name or 'N/A'} <{email or 'N/A'}>\"\n\n\ndef get_env_var(\n    env_var: str,\n    print_if_not_found: bool = True,\n    default: Optional[str] = None,\n) -> Optional[str]:\n    \"\"\"\n    Get the value of an environment variable.\n\n    :param env_var: the environment variable name/key\n    :param print_if_not_found: whether to print if the environment variable could not be found\n    :param default: the value to use if the environment variable could not be found\n    :return: the environment variable value, or `None` if not found and no default value was given\n    \"\"\"\n    value = os.environ.get(env_var, None)\n    if value is None:\n        if default is not None:\n            if print_if_not_found:\n                logger.print(\n                    f\"could not get environment variable: '{env_var}'; \"\n                    f\"using value default value: '{default}'\"\n                )\n            value = default\n        elif print_if_not_found:\n            logger.print(f\"could not get environment variable: '{env_var}'\")\n    return value\n\n\nclass CommitInfo:\n    \"\"\"Container for all necessary commit information.\"\"\"\n\n    def __init__(\n        self,\n        commit_hash: str,\n        title: str,\n        body: List[str],\n        author_name: Optional[str],\n        author_email: Optional[str],\n        is_merge_commit: bool = False,\n    ) -> None:\n        \"\"\"Create a CommitInfo object.\"\"\"\n        self.hash = commit_hash\n        self.title = title\n        self.body = body\n        self.author_name = author_name\n        self.author_email = author_email\n        self.is_merge_commit = is_merge_commit\n\n\nclass CommitDataRetriever:\n    \"\"\"\n    Abstract commit data retriever.\n\n    It first provides a method to check whether it applies to the current setup or not.\n    It also provides other methods to get commits to be checked.\n    These should not be called if it doesn't apply.\n    \"\"\"\n\n    def name(self) -> str:\n        \"\"\"Get a name that represents this retriever.\"\"\"\n        raise NotImplementedError  # pragma: no cover\n\n    def applies(self) -> bool:\n        \"\"\"Check if this retriever applies, i.e. can provide commit data.\"\"\"\n        raise NotImplementedError  # pragma: no cover\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:\n        \"\"\"\n        Get the range of commits to be checked: (last commit that was checked, latest commit).\n\n        The range excludes the first commit, e.g. ]first commit, second commit]\n\n        :return the (last commit that was checked, latest commit) tuple, or `None` if it failed\n        \"\"\"\n        raise NotImplementedError  # pragma: no cover\n\n    def get_commits(self, base: str, head: str, **kwargs: Any) -> Optional[List[CommitInfo]]:\n        \"\"\"Get commit data.\"\"\"\n        raise NotImplementedError  # pragma: no cover\n\n\nclass GitRetriever(CommitDataRetriever):\n    \"\"\"Implementation for any git repository.\"\"\"\n\n    def name(self) -> str:  # noqa: D102\n        return 'git (default)'\n\n    def applies(self) -> bool:  # noqa: D102\n        # Unless we only have access to a partial commit history\n        return True\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:  # noqa: D102\n        default_branch = options.default_branch\n        logger.verbose_print(f\"\\tusing default branch '{default_branch}'\")\n        commit_hash_base = get_common_ancestor_commit_hash(default_branch)\n        if not commit_hash_base:\n            return None\n        commit_hash_head = get_head_commit_hash()\n        if not commit_hash_head:\n            return None\n        return commit_hash_base, commit_hash_head\n\n    def get_commits(  # noqa: D102\n        self,\n        base: str,\n        head: str,\n        check_merge_commits: bool = False,\n        **kwargs: Any,\n    ) -> Optional[List[CommitInfo]]:\n        ignore_merge_commits = not check_merge_commits\n        commits_data = get_commits_data(base, head, ignore_merge_commits=ignore_merge_commits)\n        commits: List[CommitInfo] = []\n        if commits_data is None:\n            return commits\n        individual_commits = split_commits_data(commits_data)\n        for commit_data in individual_commits:\n            commit_lines = commit_data.split('\\n')\n            commit_hash = commit_lines[0]\n            commit_author_data = commit_lines[1]\n            commit_title = commit_lines[2]\n            commit_body = commit_lines[3:]\n            author_result = extract_name_and_email(commit_author_data)\n            author_name, author_email = None, None\n            if author_result:\n                author_name, author_email = author_result\n            # There won't be any merge commits at this point\n            is_merge_commit = False\n            commits.append(\n                CommitInfo(\n                    commit_hash,\n                    commit_title,\n                    commit_body,\n                    author_name,\n                    author_email,\n                    is_merge_commit,\n                )\n            )\n        return commits\n\n\nclass GitLabRetriever(GitRetriever):\n    \"\"\"Implementation for GitLab CI.\"\"\"\n\n    def name(self) -> str:  # noqa: D102\n        return 'GitLab'\n\n    def applies(self) -> bool:  # noqa: D102\n        return get_env_var('GITLAB_CI', print_if_not_found=False) is not None\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:  # noqa: D102\n        # See: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html\n        default_branch = get_env_var('CI_DEFAULT_BRANCH', default=options.default_branch)\n\n        commit_hash_head = get_env_var('CI_COMMIT_SHA')\n        if not commit_hash_head:\n            return None\n\n        current_branch = get_env_var('CI_COMMIT_BRANCH')\n        if get_env_var('CI_PIPELINE_SOURCE') == 'schedule':\n            # Do not check scheduled pipelines\n            logger.verbose_print(\"\\ton scheduled pipeline: won't check commits\")\n            return commit_hash_head, commit_hash_head\n        elif current_branch == default_branch:\n            # If we're on the default branch, just test new commits\n            logger.verbose_print(\n                f\"\\ton default branch '{current_branch}': \"\n                'will check new commits'\n            )\n            commit_hash_base = get_env_var('CI_COMMIT_BEFORE_SHA')\n            if commit_hash_base == '0000000000000000000000000000000000000000':\n                logger.verbose_print('\\tfound no new commits')\n                return commit_hash_head, commit_hash_head\n            if not commit_hash_base:\n                return None\n            return commit_hash_base, commit_hash_head\n        elif get_env_var('CI_MERGE_REQUEST_ID', print_if_not_found=False):\n            # Get merge request target branch\n            target_branch = get_env_var('CI_MERGE_REQUEST_TARGET_BRANCH_NAME')\n            if not target_branch:\n                return None\n            logger.verbose_print(\n                f\"\\ton merge request branch '{current_branch}': \"\n                f\"will check new commits off of target branch '{target_branch}'\"\n            )\n            target_branch_sha = get_env_var('CI_MERGE_REQUEST_TARGET_BRANCH_SHA')\n            if not target_branch_sha:\n                return None\n            return target_branch_sha, commit_hash_head\n        elif get_env_var('CI_EXTERNAL_PULL_REQUEST_IID', print_if_not_found=False):\n            # Get external merge request target branch\n            target_branch = get_env_var('CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME')\n            if not target_branch:\n                return None\n            logger.verbose_print(\n                f\"\\ton merge request branch '{current_branch}': \"\n                f\"will check new commits off of target branch '{target_branch}'\"\n            )\n            target_branch_sha = get_env_var('CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA')\n            if not target_branch_sha:\n                return None\n            return target_branch_sha, commit_hash_head\n        else:\n            if not default_branch:\n                return None\n            # Otherwise test all commits off of the default branch\n            logger.verbose_print(\n                f\"\\ton branch '{current_branch}': \"\n                f\"will check forked commits off of default branch '{default_branch}'\"\n            )\n            # Fetch default branch\n            remote = options.default_remote\n            if 0 != fetch_branch(default_branch, remote):\n                logger.print(f\"failed to fetch '{default_branch}' from remote '{remote}'\")\n                return None\n            # Use remote default branch ref\n            remote_branch_ref = remote + '/' + default_branch\n            commit_hash_base = get_common_ancestor_commit_hash(remote_branch_ref)\n            if not commit_hash_base:\n                return None\n            return commit_hash_base, commit_hash_head\n\n\nclass CircleCiRetriever(GitRetriever):\n    \"\"\"Implementation for CircleCI.\"\"\"\n\n    def name(self) -> str:  # noqa: D102\n        return 'CircleCI'\n\n    def applies(self) -> bool:  # noqa: D102\n        return get_env_var('CIRCLECI', print_if_not_found=False) is not None\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:  # noqa: D102\n        # See: https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables\n        default_branch = options.default_branch\n\n        commit_hash_head = get_env_var('CIRCLE_SHA1')\n        if not commit_hash_head:\n            return None\n\n        # Check if base revision is provided to the environment, e.g.\n        #   environment:\n        #     CIRCLE_BASE_REVISION: << pipeline.git.base_revision >>\n        # See:\n        #   https://circleci.com/docs/2.0/pipeline-variables/\n        #   https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables\n        base_revision = get_env_var('CIRCLE_BASE_REVISION', print_if_not_found=False)\n        if base_revision:\n            # For PRs, this is the commit of the base branch,\n            # and, for pushes to a branch, this is the commit before the new commits\n            logger.verbose_print(\n                f\"\\tchecking commits off of base revision '{base_revision}'\"\n            )\n            return base_revision, commit_hash_head\n        else:\n            current_branch = get_env_var('CIRCLE_BRANCH')\n            if not current_branch:\n                return None\n            # Test all commits off of the default branch\n            logger.verbose_print(\n                f\"\\ton branch '{current_branch}': \"\n                f\"will check forked commits off of default branch '{default_branch}'\"\n            )\n            # Fetch default branch\n            remote = options.default_remote\n            if 0 != fetch_branch(default_branch, remote):\n                logger.print(f\"failed to fetch '{default_branch}' from remote '{remote}'\")\n                return None\n            # Use remote default branch ref\n            remote_branch_ref = remote + '/' + default_branch\n            commit_hash_base = get_common_ancestor_commit_hash(remote_branch_ref)\n            if not commit_hash_base:\n                return None\n            return commit_hash_base, commit_hash_head\n\n\nclass AzurePipelinesRetriever(GitRetriever):\n    \"\"\"Implementation for Azure Pipelines.\"\"\"\n\n    def name(self) -> str:  # noqa: D102\n        return 'Azure Pipelines'\n\n    def applies(self) -> bool:  # noqa: D102\n        return get_env_var('TF_BUILD', print_if_not_found=False) is not None\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:  # noqa: D102\n        # See: https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables  # noqa: E501\n        commit_hash_head = get_env_var('BUILD_SOURCEVERSION')\n        if not commit_hash_head:\n            return None\n        current_branch = get_env_var('BUILD_SOURCEBRANCHNAME')\n        if not current_branch:\n            return None\n\n        base_branch = None\n        # Check if pull request\n        is_pull_request = get_env_var(\n            'SYSTEM_PULLREQUEST_PULLREQUESTID',\n            print_if_not_found=False,\n        )\n        if is_pull_request:\n            # Test all commits off of the target branch\n            target_branch = get_env_var('SYSTEM_PULLREQUEST_TARGETBRANCH')\n            if not target_branch:\n                return None\n            logger.verbose_print(\n                f\"\\ton pull request branch '{current_branch}': \"\n                f\"will check forked commits off of target branch '{target_branch}'\"\n            )\n            base_branch = target_branch\n        else:\n            # Test all commits off of the default branch\n            default_branch = options.default_branch\n            logger.verbose_print(\n                f\"\\ton branch '{current_branch}': \"\n                f\"will check forked commits off of default branch '{default_branch}'\"\n            )\n            base_branch = default_branch\n        # Fetch base branch\n        assert base_branch\n        remote = options.default_remote\n        if 0 != fetch_branch(base_branch, remote):\n            logger.print(f\"failed to fetch '{base_branch}' from remote '{remote}'\")\n            return None\n        # Use remote default branch ref\n        remote_branch_ref = remote + '/' + base_branch\n        commit_hash_base = get_common_ancestor_commit_hash(remote_branch_ref)\n        if not commit_hash_base:\n            return None\n        return commit_hash_base, commit_hash_head\n\n\nclass AppVeyorRetriever(GitRetriever):\n    \"\"\"Implementation for AppVeyor.\"\"\"\n\n    def name(self) -> str:  # noqa: D102\n        return 'AppVeyor'\n\n    def applies(self) -> bool:  # noqa: D102\n        return get_env_var('APPVEYOR', print_if_not_found=False) is not None\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:  # noqa: D102\n        # See: https://www.appveyor.com/docs/environment-variables/\n        default_branch = options.default_branch\n\n        commit_hash_head = get_env_var('APPVEYOR_REPO_COMMIT')\n        if not commit_hash_head:\n            commit_hash_head = get_head_commit_hash()\n            if not commit_hash_head:\n                return None\n\n        branch = get_env_var('APPVEYOR_REPO_BRANCH')\n        if not branch:\n            return None\n\n        # Check if pull request\n        if get_env_var('APPVEYOR_PULL_REQUEST_NUMBER', print_if_not_found=False):\n            current_branch = get_env_var('APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH')\n            if not current_branch:\n                return None\n            target_branch = branch\n            logger.verbose_print(\n                f\"\\ton pull request branch '{current_branch}': \"\n                f\"will check commits off of target branch '{target_branch}'\"\n            )\n            commit_hash_head = get_env_var('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') or commit_hash_head\n            if not commit_hash_head:\n                return None\n            commit_hash_base = get_common_ancestor_commit_hash(target_branch)\n            if not commit_hash_base:\n                return None\n            return commit_hash_base, commit_hash_head\n        else:\n            # Otherwise test all commits off of the default branch\n            current_branch = branch\n            logger.verbose_print(\n                f\"\\ton branch '{current_branch}': \"\n                f\"will check forked commits off of default branch '{default_branch}'\"\n            )\n            commit_hash_base = get_common_ancestor_commit_hash(default_branch)\n            if not commit_hash_base:\n                return None\n            return commit_hash_base, commit_hash_head\n\n\nclass GitHubRetriever(CommitDataRetriever):\n    \"\"\"Implementation for GitHub CI.\"\"\"\n\n    def name(self) -> str:  # noqa: D102\n        return 'GitHub CI'\n\n    def applies(self) -> bool:  # noqa: D102\n        return get_env_var('GITHUB_ACTIONS', print_if_not_found=False) == 'true'\n\n    def get_commit_range(self) -> Optional[Tuple[str, str]]:  # noqa: D102\n        # See: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html\n        self.github_token = get_env_var('GITHUB_TOKEN')\n        if not self.github_token:\n            logger.print('Did you forget to include this in your workflow config?')\n            logger.print('\\n\\tenv:\\n\\t\\tGITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}')\n            return None\n\n        # See: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables  # noqa: E501\n        event_payload_path = get_env_var('GITHUB_EVENT_PATH')\n        if not event_payload_path:\n            return None\n        f = open(event_payload_path)\n        self.event_payload = json.load(f)\n        f.close()\n\n        # Get base & head commits depending on the workflow event type\n        event_name = get_env_var('GITHUB_EVENT_NAME')\n        if not event_name:\n            return None\n        commit_hash_base = None\n        commit_hash_head = None\n        if event_name in ('pull_request', 'pull_request_target'):\n            # See: https://developer.github.com/v3/activity/events/types/#pullrequestevent\n            commit_hash_base = self.event_payload['pull_request']['base']['sha']\n            commit_hash_head = self.event_payload['pull_request']['head']['sha']\n            commit_branch_base = self.event_payload['pull_request']['base']['ref']\n            commit_branch_head = self.event_payload['pull_request']['head']['ref']\n            logger.verbose_print(\n                f\"\\ton pull request branch '{commit_branch_head}': \"\n                f\"will check commits off of base branch '{commit_branch_base}'\"\n            )\n        elif event_name == 'push':\n            # See: https://developer.github.com/v3/activity/events/types/#pushevent\n            created = self.event_payload['created']\n            if created:\n                # If the branch was just created, there won't be a 'before' commit,\n                # therefore just get the first commit in the new branch and append '^'\n                # to get the commit before that one\n                commits = self.event_payload['commits']\n                # TODO check len(commits),\n                # it's probably 0 when pushing a new branch that is based on an existing one\n                commit_hash_base = commits[0]['id'] + '^'\n            else:\n                commit_hash_base = self.event_payload['before']\n            commit_hash_head = self.event_payload['head_commit']['id']\n        else:  # pragma: no cover\n            logger.print('Unknown workflow event:', event_name)\n            return None\n        return commit_hash_base, commit_hash_head\n\n    def get_commits(  # noqa: D102\n        self,\n        base: str,\n        head: str,\n        **kwargs: Any,\n    ) -> Optional[List[CommitInfo]]:\n        # Request commit data\n        compare_url_template = self.event_payload['repository']['compare_url']\n        compare_url = compare_url_template.format(base=base, head=head)\n        req = request.Request(compare_url, headers={\n            'User-Agent': 'dco_check',\n            'Authorization': 'token ' + (self.github_token or ''),\n        })\n        response = request.urlopen(req)\n        if 200 != response.getcode():  # pragma: no cover\n            from pprint import pformat\n            logger.print('Request failed: compare_url')\n            logger.print('reponse:', pformat(response.read().decode()))\n            return None\n\n        # Extract data\n        response_json = json.load(response)\n        commits = []\n        for commit in response_json['commits']:\n            commit_hash = commit['sha']\n            message = commit['commit']['message'].split('\\n')\n            message = list(filter(None, message))\n            commit_title = message[0]\n            commit_body = message[1:]\n            author_name = commit['commit']['author']['name']\n            author_email = commit['commit']['author']['email']\n            is_merge_commit = len(commit['parents']) > 1\n            commits.append(\n                CommitInfo(\n                    commit_hash,\n                    commit_title,\n                    commit_body,\n                    author_name,\n                    author_email,\n                    is_merge_commit,\n                )\n            )\n        return commits\n\n\ndef process_commits(\n    commits: List[CommitInfo],\n    check_merge_commits: bool,\n) -> Dict[str, List[str]]:\n    \"\"\"\n    Process commit information to detect DCO infractions.\n\n    :param commits: the list of commit info\n    :param check_merge_commits: true to check merge commits, false otherwise\n    :return: the infractions as a dict {commit sha, infraction explanation}\n    \"\"\"\n    infractions: Dict[str, List[str]] = defaultdict(list)\n    for commit in commits:\n        # Skip this commit if it is a merge commit and the\n        # option for checking merge commits is not enabled\n        if commit.is_merge_commit and not check_merge_commits:\n            logger.verbose_print('\\t' + 'ignoring merge commit:', commit.hash)\n            logger.verbose_print()\n            continue\n\n        logger.verbose_print(\n            '\\t' + commit.hash + (' (merge commit)' if commit.is_merge_commit else '')\n        )\n        logger.verbose_print('\\t' + format_name_and_email(commit.author_name, commit.author_email))\n        logger.verbose_print('\\t' + commit.title)\n        logger.verbose_print('\\t' + '\\n\\t'.join(commit.body))\n\n        # Check author name and email\n        if any(not d for d in [commit.author_name, commit.author_email]):\n            infractions[commit.hash].append(\n                f'could not extract author data for commit: {commit.hash}'\n            )\n            continue\n\n        # Check if the commit should be ignored because of the commit author email\n        if options.exclude_emails and commit.author_email in options.exclude_emails:\n            logger.verbose_print('\\t\\texcluding commit since author email is in exclude list')\n            logger.verbose_print()\n            continue\n\n        # Check if the commit should be ignored because of the commit author email pattern\n        if commit.author_email and options.exclude_pattern:\n            if options.exclude_pattern.search(commit.author_email):\n                logger.verbose_print('\\t\\texcluding commit since author email is matched by')\n                logger.verbose_print('\\t\\tpattern')\n                logger.verbose_print()\n                continue\n\n        # Extract sign-off data\n        sign_offs = [\n            body_line.replace(TRAILER_KEY_SIGNED_OFF_BY, '').strip(' ')\n            for body_line in commit.body\n            if body_line.startswith(TRAILER_KEY_SIGNED_OFF_BY)\n        ]\n\n        # Check that there is at least one sign-off right away\n        if len(sign_offs) == 0:\n            infractions[commit.hash].append('no sign-off found')\n            continue\n\n        # Extract sign off information\n        sign_offs_name_email: List[Tuple[str, str]] = []\n        for sign_off in sign_offs:\n            sign_off_result = extract_name_and_email(sign_off)\n            if not sign_off_result:\n                continue\n            name, email = sign_off_result\n            logger.verbose_print(f'\\t\\tfound sign-off: {format_name_and_email(name, email)}')\n            if not is_valid_email(email):\n                infractions[commit.hash].append(f'invalid email: {email}')\n            else:\n                sign_offs_name_email.append((name, email.lower()))\n\n        # Check that author is in the sign-offs\n        if not (commit.author_name, commit.author_email.lower()) in sign_offs_name_email:\n            infractions[commit.hash].append(\n                'sign-off not found for commit author: '\n                f'{commit.author_name} {commit.author_email}; found: {sign_offs}'\n            )\n\n        # Separator between commits\n        logger.verbose_print()\n\n    return infractions\n\n\ndef check_infractions(\n    infractions: Dict[str, List[str]],\n) -> int:\n    \"\"\"\n    Check infractions.\n\n    :param infractions: the infractions dict {commit sha, infraction explanation}\n    :return: 0 if no infractions, non-zero otherwise\n    \"\"\"\n    if len(infractions) > 0:\n        logger.print('Missing sign-off(s):')\n        logger.print()\n        for commit_sha, commit_infractions in infractions.items():\n            logger.print('\\t' + commit_sha)\n            for commit_infraction in commit_infractions:\n                logger.print('\\t\\t' + commit_infraction)\n        return 1\n    logger.print('All good!')\n    return 0\n\n\ndef main(argv: Optional[List[str]] = None) -> int:\n    \"\"\"\n    Entrypoint.\n\n    :param argv: the arguments to use, or `None` for sys.argv\n    :return: 0 if successful, non-zero otherwise\n    \"\"\"\n    args = parse_args(argv)\n    options.set_options(args)\n    logger.set_options(options)\n\n    # Print options\n    if options.verbose:\n        logger.verbose_print('Options:')\n        for name, value in options.get_options().items():\n            logger.verbose_print(f'\\t{name}: {str(value)}')\n        logger.verbose_print()\n\n    # Detect CI\n    # Use first one that applies\n    retrievers = [\n        GitLabRetriever,\n        GitHubRetriever,\n        AzurePipelinesRetriever,\n        AppVeyorRetriever,\n        CircleCiRetriever,\n        GitRetriever,\n    ]\n    commit_retriever = None\n    for retriever_cls in retrievers:\n        retriever = retriever_cls()\n        if retriever.applies():\n            commit_retriever = retriever\n            break\n    if not commit_retriever:\n        logger.print('Could not find an applicable GitRetriever')\n        return 1\n    logger.print('Detected:', commit_retriever.name())\n\n    # Get default branch from remote if enabled\n    if options.default_branch_from_remote:\n        remote_default_branch = get_default_branch_from_remote(options.default_remote)\n        if not remote_default_branch:\n            logger.print('Could not get default branch from remote')\n            return 1\n        options.default_branch = remote_default_branch\n        logger.print(f\"\\tgot default branch '{remote_default_branch}' from remote\")\n\n    # Get range of commits\n    commit_range = commit_retriever.get_commit_range()\n    if not commit_range:\n        return 1\n    commit_hash_base, commit_hash_head = commit_range\n\n    logger.print()\n    # Return success now if base == head\n    if commit_hash_base == commit_hash_head:\n        logger.print('No commits to check')\n        return 0\n\n    logger.print(f'Checking commits: {commit_hash_base}..{commit_hash_head}')\n    logger.print()\n\n    # Get commits\n    commits = commit_retriever.get_commits(\n        commit_hash_base,\n        commit_hash_head,\n        check_merge_commits=options.check_merge_commits,\n    )\n    if commits is None:\n        return 1\n\n    # Process them\n    infractions = process_commits(commits, options.check_merge_commits)\n\n    # Check if there are any infractions\n    result = check_infractions(infractions)\n\n    if len(commits) == 0:\n        logger.print('Warning: no commits were actually checked')\n\n    return result\n\n\nif __name__ == '__main__':  # pragma: no cover\n    sys.exit(main())\n"
  },
  {
    "path": "scripts/lint/import-order-cleanup.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport argparse\n\ndef cleanup_imports_and_return(imports):\n    os_packages = []\n    jaeger_packages = []\n    thirdparty_packages = []\n    for i in imports:\n        if i.strip() == \"\":\n            continue\n        if i.find(\"github.com/jaegertracing/jaeger/\") != -1 or i.find(\"github.com/jaegertracing/jaeger-idl/\") != -1:\n            jaeger_packages.append(i)\n        elif i.find(\".com\") != -1 or i.find(\".net\") != -1 or i.find(\".org\") != -1 or i.find(\".in\") != -1 or i.find(\".io\") != -1:\n            thirdparty_packages.append(i)\n        else:\n            os_packages.append(i)\n\n    l = []\n    needs_new_line = False\n    if os_packages:\n        l.extend(os_packages)\n        needs_new_line = True\n    if thirdparty_packages:\n        if needs_new_line:\n            l.append(\"\")\n        l.extend(thirdparty_packages)\n        needs_new_line = True\n    if jaeger_packages:\n        if needs_new_line:\n            l.append(\"\")\n        l.extend(jaeger_packages)\n\n    imports_reordered = imports != l\n    l.insert(0, \"import (\")\n    l.append(\")\")\n    return l, imports_reordered\n\ndef parse_go_file(f):\n    with open(f, 'r') as go_file:\n        lines = [i.rstrip() for i in go_file.readlines()]\n        in_import_block = False\n        imports_reordered = False\n        imports = []\n        output_lines = []\n        for line in lines:\n            if in_import_block:\n                endIdx = line.find(\")\")\n                if endIdx != -1:\n                    in_import_block = False\n                    ordered_imports, imports_reordered = cleanup_imports_and_return(imports)\n                    output_lines.extend(ordered_imports)\n                    imports = []\n                    continue\n                imports.append(line)\n            else:\n                importIdx = line.find(\"import (\")\n                if importIdx != -1:\n                    in_import_block = True\n                    continue\n                output_lines.append(line)\n        output_lines.append(\"\")\n        return \"\\n\".join(output_lines), imports_reordered\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description='Tool to make cleaning up import orders easily')\n\n    parser.add_argument('-o', '--output', default='stdout',\n                        choices=['inplace', 'stdout'],\n                        help='output target [default: stdout]')\n\n    parser.add_argument('-t', '--target',\n                        help='list of filenames to operate upon',\n                        nargs='+',\n                        required=True)\n\n    args = parser.parse_args()\n    output = args.output\n    go_files = args.target\n\n    for f in go_files:\n        parsed, imports_reordered = parse_go_file(f)\n        if output == \"stdout\":\n            if imports_reordered:\n                print(f + \" imports out of order\")\n        else:\n            with open(f, 'w') as ofile:\n                ofile.write(parsed)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/lint/replace_license_headers.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Replace Apache 2.0 license headers with SPDX license identifiers.\n\nimport re\nimport sys\n\ndef replace_license_header(file_path, dry_run=False):\n    with open(file_path, 'r') as file:\n        content = file.read()\n\n    # Pattern to match the entire old header, including multiple copyright lines\n    header_pattern = re.compile(r'(?s)^(// Copyright.*?(?:\\n// Copyright.*?)*\\n//\\n// Licensed under the Apache License.*?limitations under the License\\.)\\s*\\n')\n    \n    match = header_pattern.match(content)\n    if match:\n        old_header = match.group(1)\n        if \"SPDX-License-Identifier: Apache-2.0\" in old_header:\n            print(f\"Skipping {file_path}: SPDX identifier already present\")\n            return False\n        \n        if dry_run:\n            print(f\"Would update {file_path}\")\n            return True\n        \n        # Preserve all copyright lines and add SPDX identifier\n        copyright_lines = re.findall(r'// Copyright.*', old_header)\n        new_header = \"\\n\".join(copyright_lines) + \"\\n// SPDX-License-Identifier: Apache-2.0\\n\\n\"\n        \n        new_content = header_pattern.sub(new_header, content, count=1)\n        \n        with open(file_path, 'w') as file:\n            file.write(new_content)\n        print(f\"Updated {file_path}\")\n        return True\n    else:\n        print(f\"Warning: {file_path} - Could not find expected license header\")\n        return False\n\ndef main():\n    dry_run = '--dry-run' in sys.argv\n    files = [f for f in sys.argv[1:] if f != '--dry-run']\n    \n    if not files:\n        print(\"Usage: python replace_license_headers.py [--dry-run] <file> [<file> ...]\")\n        sys.exit(1)\n    \n    if dry_run:\n        print(\"Performing dry run - no files will be modified\")\n    \n    total_updated = sum(replace_license_header(file, dry_run) for file in files)\n    print(f\"Total files {'that would be' if dry_run else ''} updated: {total_updated}\")\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/lint/update-semconv-version.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\npackage_name=\"go.opentelemetry.io/otel/semconv\"\nversion_regex=\"v[0-9]\\.[0-9]\\+\\.[0-9]\\+\"\n\nlatest_semconv_version=$(\n    curl -s https://pkg.go.dev/$package_name \\\n    | grep -oP 'data-id=\"v\\d+\\.\\d+\\.\\d+\"' \\\n    | sed -E \"s/\\\"($version_regex)\\\"/v\\1/\" \\\n    | sort -Vr \\\n    | head -n 1 \\\n    | awk -F'\"' '{print $2}'\n)\n\nlatest_package_string=\"$package_name/$latest_semconv_version\"\n\nwhile IFS=: read -r file_name package_string; do\n    version_number=${package_string##*/}\n\n    if [ \"$version_number\" != \"$latest_semconv_version\" ]; then\n\tsed -i \"s#$package_name/$version_regex#$latest_package_string#g\" \"$file_name\"\n\t{\n            printf \"Source File: %-60s | Previous Semconv Version: %s | Updated Semconv Version: %s\\n\" \"$file_name\" \"$version_number\" \"$latest_semconv_version\"\n        } | column -t -s '|'\n    fi\ndone < <(find . -type f -name \"*.go\" -exec grep -o -H \"$package_name/$version_regex\" {} +)\n"
  },
  {
    "path": "scripts/lint/updateLicense.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport logging\nimport re\nimport sys\nfrom datetime import datetime\n\nlogging.basicConfig(level=logging.DEBUG)\nlogger = logging.getLogger(__name__)\n\nCURRENT_YEAR = datetime.today().year\n\nLICENSE_BLOB = \"\"\"Copyright (c) %d The Jaeger Authors.\nSPDX-License-Identifier: Apache-2.0\"\"\" % CURRENT_YEAR\n\ndef get_license_blob_lines(comment_prefix):\n    return [\n        (comment_prefix + ' ' + l).strip() + '\\n' for l in LICENSE_BLOB.split('\\n')\n    ]\n\nCOPYRIGHT_RE = re.compile(r'Copyright \\(c\\) (\\d+)', re.I)\n\nSHEBANG_RE = re.compile(r'^#!\\s*/[^\\s]+')\n\ndef update_license(name, license_lines):\n    with open(name) as f:\n        orig_lines = list(f)\n    lines = list(orig_lines)\n\n    found = False\n    changed = False\n    jaeger = False\n    for i, line in enumerate(lines[:5]):\n        m = COPYRIGHT_RE.search(line)\n        if not m:\n            continue\n\n        found = True\n        jaeger = 'Jaeger' in line\n\n        year = int(m.group(1))\n        if year == CURRENT_YEAR:\n            break\n\n        # Avoid updating the copyright year.\n        #\n        # new_line = COPYRIGHT_RE.sub('Copyright (c) %d' % CURRENT_YEAR, line)\n        # assert line != new_line, ('Could not change year in: %s' % line)\n        # lines[i] = new_line\n        # changed = True\n        break\n\n    # print('found=%s, changed=%s, jaeger=%s' % (found, changed, jaeger))\n\n    first_line = lines[0]\n    shebang_match = SHEBANG_RE.match(first_line)\n        \n    def replace(header_lines):\n\n        if 'Code generated by' in first_line:\n            lines[1:1] = ['\\n'] + header_lines\n        elif shebang_match:\n            lines[1:1] = header_lines\n        else:\n            lines[0:0] = header_lines\n\n    if not found:\n        # depend on file type\n        if(shebang_match):\n            replace(['\\n'] + license_lines)\n        else:\n            replace(license_lines + ['\\n'])\n\n        changed = True\n    else:\n        if not jaeger:\n            replace(license_lines[0])            \n            changed = True\n\n    if changed:\n        with open(name, 'w') as f:\n            for line in lines:\n                f.write(line)\n        print(name)\n\ndef get_license_type(file):\n    license_blob_lines_go = get_license_blob_lines('//')\n    license_blob_lines_script = get_license_blob_lines('#')\n\n    ext_map = {\n        '.go' : license_blob_lines_go,\n        '.mk' : license_blob_lines_script,\n        'Makefile' : license_blob_lines_script,\n        'Dockerfile' : license_blob_lines_script,\n        '.py' : license_blob_lines_script,\n        '.sh' : license_blob_lines_script,\n    }\n\n    license_type = None\n\n    for ext, license in ext_map.items():\n        if file.endswith(ext):\n            license_type = license\n            break\n\n    return license_type\n\ndef main():\n    if len(sys.argv) == 1:\n        print('USAGE: %s FILE ...' % sys.argv[0])\n        sys.exit(1)\n\n    for name in sys.argv[1:]:\n        license_type = get_license_type(name)\n        if license_type:\n            try:\n                update_license(name, license_type)\n            except Exception as error:\n                logger.error('Failed to process file %s', name)\n                logger.exception(error)\n                raise error\n        else:\n            raise NotImplementedError('Unsupported file type: %s' % name)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/makefiles/BuildBinaries.mk",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nGOBUILD_EXEC := CGO_ENABLED=0 installsuffix=cgo $(GO) build -trimpath\nSTYLE_BOLD_BLUE := \\e[1m\\e[34m\nSTYLE_BOLD_ORANGE := \\033[1m\\033[38;5;208m\nSTYLE_RESET := \\e[39m\\e[0m\n\n# This macro expects $GOOS/$GOARCH env variables set to reflect the desired target platform.\n# It also expects one argument: the name of the binary being built.\ndefine GOBUILD\n@printf \"🚧 building binary '$(STYLE_BOLD_ORANGE)%s$(STYLE_RESET)' for $$(go env GOOS)-$$(go env GOARCH)\\n\" \"$1\"\n$(GOBUILD_EXEC)\nendef\n\nifeq ($(DEBUG_BINARY),)\n\tDISABLE_OPTIMIZATIONS =\n\tSUFFIX =\n\tTARGET = release\nelse\n\tDISABLE_OPTIMIZATIONS = -gcflags=\"all=-N -l\"\n\tSUFFIX = -debug\n\tTARGET = debug\nendif\n\n.PHONY: build-ui\nbuild-ui: cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual/index.html.gz\n\ncmd/jaeger/internal/extension/jaegerquery/internal/ui/actual/index.html.gz: jaeger-ui/packages/jaeger-ui/build/index.html\n\t# do not delete dot-files\n\trm -rf cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual/*\n\tcp -r jaeger-ui/packages/jaeger-ui/build/* cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual/\n\tfind cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual -type f | grep -v .gitignore | xargs gzip --no-name\n\t# copy the timestamp for index.html.gz from the original file\n\ttouch -t $$(date -r jaeger-ui/packages/jaeger-ui/build/index.html '+%Y%m%d%H%M.%S') cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual/index.html.gz\n\tls -lF cmd/jaeger/internal/extension/jaegerquery/internal/ui/actual/\n\njaeger-ui/packages/jaeger-ui/build/index.html:\n\t$(MAKE) rebuild-ui\n\n.PHONY: rebuild-ui\nrebuild-ui:\n\t@echo \"::group::rebuild-ui logs\"\n\tbash ./scripts/build/rebuild-ui.sh\n\t@echo \"NOTE: This target only rebuilds the UI assets inside jaeger-ui/packages/jaeger-ui/build/.\"\n\t@echo \"NOTE: To make them usable from query-service run 'make build-ui'.\"\n\t@echo \"::endgroup::\"\n\n.PHONY: build-examples\nbuild-examples:\n\t$(GOBUILD) -o ./examples/hotrod/hotrod-$(GOOS)-$(GOARCH) ./examples/hotrod/main.go\n\n.PHONY: build-tracegen\nbuild-tracegen:\n\t$(call GOBUILD,tracegen) -o ./cmd/tracegen/tracegen-$(GOOS)-$(GOARCH) ./cmd/tracegen/\n\n.PHONY: build-anonymizer\nbuild-anonymizer:\n\t$(call GOBUILD,anonymizer) -o ./cmd/anonymizer/anonymizer-$(GOOS)-$(GOARCH) ./cmd/anonymizer/\n\n.PHONY: build-esmapping-generator\nbuild-esmapping-generator:\n\t$(call GOBUILD,esmapping-generator) -o ./cmd/esmapping-generator/esmapping-generator-$(GOOS)-$(GOARCH) ./cmd/esmapping-generator/\n\n.PHONY: build-es-index-cleaner\nbuild-es-index-cleaner:\n\t$(call GOBUILD,es-index-cleaner) -o ./cmd/es-index-cleaner/es-index-cleaner-$(GOOS)-$(GOARCH) ./cmd/es-index-cleaner/\n\n.PHONY: build-es-rollover\nbuild-es-rollover:\n\t$(call GOBUILD,es-rollover) -o ./cmd/es-rollover/es-rollover-$(GOOS)-$(GOARCH) ./cmd/es-rollover/\n\n# Requires variables: $(BIN_NAME) $(BIN_PATH) $(GO_TAGS) $(DISABLE_OPTIMIZATIONS) $(SUFFIX) $(GOOS) $(GOARCH) $(BUILD_INFO)\n# Other targets can depend on this one but with a unique suffix to ensure it is always executed.\nBIN_PATH = ./cmd/$(BIN_NAME)\n.PHONY: _build-a-binary\n_build-a-binary-%:\n\t$(call GOBUILD,$(BIN_PATH)) $(DISABLE_OPTIMIZATIONS) $(GO_TAGS) -o $(BIN_PATH)/$(BIN_NAME)$(SUFFIX)-$(GOOS)-$(GOARCH) $(BUILD_INFO) $(BIN_PATH)\n\n.PHONY: build-jaeger\nbuild-jaeger: BIN_NAME = jaeger\nbuild-jaeger: build-ui _build-a-binary-jaeger$(SUFFIX)-$(GOOS)-$(GOARCH)\n\t@ set -euf -o pipefail ; \\\n\techo \"Checking version of built binary\" ; \\\n\tREAL_GOOS=$(shell GOOS= $(GO) env GOOS) ; \\\n\tREAL_GOARCH=$(shell GOARCH= $(GO) env GOARCH) ; \\\n\tif [ \"$(GOOS)\" == \"$$REAL_GOOS\" ] && [ \"$(GOARCH)\" == \"$$REAL_GOARCH\" ]; then \\\n\t\t./cmd/jaeger/jaeger$(SUFFIX)-$(GOOS)-$(GOARCH) version 2>/dev/null ; \\\n\t\techo \"\" ; \\\n\t\twant=$(GIT_CLOSEST_TAG) ; \\\n\t\thave=$$(./cmd/jaeger/jaeger$(SUFFIX)-$(GOOS)-$(GOARCH) version 2>/dev/null | jq -r .gitVersion) ; \\\n\t\tif [ \"$$want\" == \"$$have\" ]; then \\\n\t\t\techo \"☑️ versions match: want=$$want, have=$$have\" ; \\\n\t\telse \\\n\t\t\techo \"❌ ERROR: version mismatch: want=$$want, have=$$have\" ; \\\n\t\t\tfalse; \\\n\t\tfi ; \\\n\telse \\\n\t\techo \".. skipping version check for cross-compilation\" ; \\\n\t\techo \".. see build-binaries-$(GOOS)-$(GOARCH)\" ; \\\n\tfi\n\n.PHONY: build-remote-storage\nbuild-remote-storage: BIN_NAME = remote-storage\nbuild-remote-storage: _build-a-binary-remote-storage$(SUFFIX)-$(GOOS)-$(GOARCH)\n\n# build all binaries for the current platform\n.PHONY: build-binaries\nbuild-binaries: _build-platform-binaries\n\n.PHONY: build-binaries-linux-amd64\nbuild-binaries-linux-amd64:\n\tGOOS=linux GOARCH=amd64 $(MAKE) _build-platform-binaries\n\n# helper sysp targets are defined in Makefile.Windows.mk\n.PHONY: build-binaries-windows-amd64\nbuild-binaries-windows-amd64:\n\t$(MAKE) _build-syso\n\tGOOS=windows GOARCH=amd64 $(MAKE) _build-platform-binaries\n\t$(MAKE) _clean-syso\n\n.PHONY: build-binaries-darwin-amd64\nbuild-binaries-darwin-amd64:\n\tGOOS=darwin GOARCH=amd64 $(MAKE) _build-platform-binaries\n\n.PHONY: build-binaries-darwin-arm64\nbuild-binaries-darwin-arm64:\n\tGOOS=darwin GOARCH=arm64 $(MAKE) _build-platform-binaries\n\n.PHONY: build-binaries-linux-s390x\nbuild-binaries-linux-s390x:\n\tGOOS=linux GOARCH=s390x $(MAKE) _build-platform-binaries\n\n.PHONY: build-binaries-linux-arm64\nbuild-binaries-linux-arm64:\n\tGOOS=linux GOARCH=arm64 $(MAKE) _build-platform-binaries\n\n.PHONY: build-binaries-linux-ppc64le\nbuild-binaries-linux-ppc64le:\n\tGOOS=linux GOARCH=ppc64le $(MAKE) _build-platform-binaries\n\n# build all binaries for one specific platform GOOS/GOARCH\n.PHONY: _build-platform-binaries\n_build-platform-binaries: \\\n\t\tbuild-jaeger \\\n\t\tbuild-remote-storage \\\n\t\tbuild-examples \\\n\t\tbuild-tracegen \\\n\t\tbuild-anonymizer \\\n\t\tbuild-esmapping-generator \\\n\t\tbuild-es-index-cleaner \\\n\t\tbuild-es-rollover\n# invoke make recursively such that DEBUG_BINARY=1 can take effect\n# skip debug builds if SKIP_DEBUG_BINARIES is set to 1 (e.g., during PRs to save CI time)\nifneq ($(SKIP_DEBUG_BINARIES),1)\n\t$(MAKE) _build-platform-binaries-debug GOOS=$(GOOS) GOARCH=$(GOARCH)\nendif\n\n# build binaries that support DEBUG release, for one specific platform GOOS/GOARCH\n# Uses recursive make calls so that DEBUG_BINARY=1 is set at parse time,\n# ensuring SUFFIX=-debug is correctly evaluated in the ifeq conditional at the top.\n.PHONY: _build-platform-binaries-debug\n_build-platform-binaries-debug:\n\t$(MAKE) build-jaeger build-remote-storage GOOS=$(GOOS) GOARCH=$(GOARCH) DEBUG_BINARY=1\n\n.PHONY: build-all-platforms\nbuild-all-platforms:\n\tfor platform in $$(echo \"$(PLATFORMS)\" | tr ',' ' ' | tr '/' '-'); do \\\n\t  echo \"Building binaries for $$platform\"; \\\n\t  $(MAKE) build-binaries-$$platform; \\\n\tdone\n"
  },
  {
    "path": "scripts/makefiles/BuildInfo.mk",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nGIT_SHA=$(shell git rev-parse HEAD)\nDATE=$(shell TZ=UTC0 git show --quiet --date='format-local:%Y-%m-%dT%H:%M:%SZ' --format=\"%cd\")\n# Defer evaluation of semver tags until actually needed, using trick from StackOverflow:\n# https://stackoverflow.com/questions/44114466/how-to-declare-a-deferred-variable-that-is-computed-only-once-for-all\nGIT_CLOSEST_TAG = $(eval GIT_CLOSEST_TAG := $(shell scripts/utils/compute-version.sh))$(GIT_CLOSEST_TAG)\n\n# args: (1) - name, (2) - value\ndefine buildinfo\n  $(JAEGER_IMPORT_PATH)/internal/version.$(1)=$(2)\nendef\ndefine buildinfoflags\n  -ldflags \"-X $(call buildinfo,commitSHA,$(GIT_SHA)) -X $(call buildinfo,latestVersion,$(GIT_CLOSEST_TAG)) -X $(call buildinfo,date,$(DATE))\"\nendef\nBUILD_INFO=$(call buildinfoflags)\n"
  },
  {
    "path": "scripts/makefiles/Docker.mk",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nDOCKER_NAMESPACE ?= jaegertracing\nDOCKER_TAG       ?= latest\nDOCKER_REGISTRY  ?= localhost:5000\nBASE_IMAGE       ?= $(DOCKER_REGISTRY)/baseimg_alpine:latest\nDEBUG_IMAGE      ?= $(DOCKER_REGISTRY)/debugimg_alpine:latest\n\ncreate-baseimg-debugimg: create-baseimg create-debugimg\n\ncreate-baseimg: prepare-docker-buildx\n\t@echo \"::group:: create-baseimg\"\n\tdocker buildx build -t $(BASE_IMAGE) --push \\\n\t\t--platform=$(LINUX_PLATFORMS) \\\n\t\tscripts/build/docker/base\n\t@echo \"::endgroup::\"\n\ncreate-debugimg: prepare-docker-buildx\n\t@echo \"::group:: create-debugimg\"\n\tdocker buildx build -t $(DEBUG_IMAGE) --push \\\n\t\t--platform=$(LINUX_PLATFORMS) \\\n\t\tscripts/build/docker/debug\n\t@echo \"::endgroup::\"\n\ncreate-fake-debugimg: prepare-docker-buildx\n\t@echo \"::group:: create-fake-debugimg\"\n\tdocker buildx build -t $(DEBUG_IMAGE) --push \\\n\t\t--platform=$(LINUX_PLATFORMS) \\\n\t\tscripts/build/docker/base\n\t@echo \"::endgroup::\"\n\n.PHONY: prepare-docker-buildx\nprepare-docker-buildx:\n\t@echo \"::group:: prepare-docker-buildx\"\n\tdocker buildx inspect jaeger-build > /dev/null || docker buildx create --use --name=jaeger-build --buildkitd-flags=\"--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host\" --driver-opt=\"network=host\"\n\tdocker inspect registry > /dev/null || docker run --rm -d -p 5000:5000 --name registry registry:2\n\t@echo \"::endgroup::\"\n\n.PHONY: clean-docker-buildx\nclean-docker-buildx:\n\tdocker buildx rm jaeger-build\n\tdocker rm -f registry\n\n.PHONY: docker-hotrod\ndocker-hotrod:\n\tGOOS=linux $(MAKE) build-examples\n\tdocker build -t $(DOCKER_NAMESPACE)/example-hotrod:${DOCKER_TAG} ./examples/hotrod --build-arg TARGETARCH=$(GOARCH)\n\t@echo \"Finished building hotrod ==============\"\n\n.PHONY: docker-images-tracegen\ndocker-images-tracegen:\n\tdocker build -t $(DOCKER_NAMESPACE)/jaeger-tracegen:${DOCKER_TAG} cmd/tracegen/ --build-arg TARGETARCH=$(GOARCH)\n\t@echo \"Finished building jaeger-tracegen ==============\"\n\n.PHONY: docker-images-anonymizer\ndocker-images-anonymizer:\n\tdocker build -t $(DOCKER_NAMESPACE)/jaeger-anonymizer:${DOCKER_TAG} cmd/anonymizer/ --build-arg TARGETARCH=$(GOARCH)\n\t@echo \"Finished building jaeger-anonymizer ==============\"\n\n.PHONY: docker-images-cassandra\ndocker-images-cassandra:\n\tdocker build -t $(DOCKER_NAMESPACE)/jaeger-cassandra-schema:${DOCKER_TAG} internal/storage/v1/cassandra/\n\t@echo \"Finished building jaeger-cassandra-schema ==============\"\n\n.PHONY: docker-images-elastic\ndocker-images-elastic: create-baseimg\n\tGOOS=linux GOARCH=$(GOARCH) $(MAKE) build-esmapping-generator\n\tGOOS=linux GOARCH=$(GOARCH) $(MAKE) build-es-index-cleaner\n\tGOOS=linux GOARCH=$(GOARCH) $(MAKE) build-es-rollover\n\tdocker build -t $(DOCKER_NAMESPACE)/jaeger-es-index-cleaner:${DOCKER_TAG} --build-arg base_image=$(BASE_IMAGE) --build-arg TARGETARCH=$(GOARCH) cmd/es-index-cleaner\n\tdocker build -t $(DOCKER_NAMESPACE)/jaeger-es-rollover:${DOCKER_TAG} --build-arg base_image=$(BASE_IMAGE) --build-arg TARGETARCH=$(GOARCH) cmd/es-rollover\n\t@echo \"Finished building jaeger-elasticsearch tools ==============\"\n"
  },
  {
    "path": "scripts/makefiles/IntegrationTests.mk",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nSTORAGE_PKGS = ./internal/storage/integration/...\nJAEGER_V2_STORAGE_PKGS = ./cmd/jaeger/internal/integration\n\n.PHONY: all-in-one-integration-test\nall-in-one-integration-test:\n\tTEST_MODE=integration $(GOTEST) ./cmd/jaeger/internal/all_in_one_test.go\n\n# A general integration tests for jaeger-v2 storage backends,\n# these tests placed at `./cmd/jaeger/internal/integration/*_test.go`.\n# The integration tests are filtered by STORAGE env.\n.PHONY: jaeger-v2-storage-integration-test\njaeger-v2-storage-integration-test:\n\t(cd cmd/jaeger/ && go build .)\n\t# Expire tests results for jaeger storage integration tests since the environment\n\t# might have changed even though the code remains the same.\n\tgo clean -testcache\n\tbash -c \"set -e; set -o pipefail; $(GOTEST) -coverpkg=./... -coverprofile $(COVEROUT) $(JAEGER_V2_STORAGE_PKGS) $(COLORIZE)\"\n\n.PHONY: storage-integration-test\nstorage-integration-test:\n\t# Expire tests results for storage integration tests since the environment might change\n\t# even though the code remains the same.\n\tgo clean -testcache\n\tbash -c \"set -e; set -o pipefail; $(GOTEST) -coverpkg=./... -coverprofile $(COVEROUT) $(STORAGE_PKGS) $(COLORIZE)\"\n\n.PHONY: badger-storage-integration-test\nbadger-storage-integration-test:\n\tSTORAGE=badger $(MAKE) storage-integration-test\n\n.PHONY: grpc-storage-integration-test\ngrpc-storage-integration-test:\n\tSTORAGE=grpc $(MAKE) storage-integration-test\n\n# this test assumes STORAGE environment variable is set to elasticsearch|opensearch\n.PHONY: index-cleaner-integration-test\nindex-cleaner-integration-test: docker-images-elastic\n\t$(MAKE) storage-integration-test COVEROUT=cover-index-cleaner.out\n\n# this test assumes STORAGE environment variable is set to elasticsearch|opensearch\n.PHONY: index-rollover-integration-test\nindex-rollover-integration-test: docker-images-elastic\n\t$(MAKE) storage-integration-test COVEROUT=cover-index-rollover.out\n\n.PHONY: tail-sampling-integration-test\ntail-sampling-integration-test:\n\tSAMPLING=tail $(MAKE) jaeger-v2-storage-integration-test\n"
  },
  {
    "path": "scripts/makefiles/Protobuf.mk",
    "content": "# Copyright (c) 2023 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Generate gogo, swagger, go-validators, gRPC-storage-plugin output.\n#\n# -I declares import folders, in order of importance. This is how proto resolves the protofile imports.\n# It will check for the protofile relative to each of thesefolders and use the first one it finds.\n#\n# --gogo_out generates GoGo Protobuf output with gRPC plugin enabled.\n# --govalidators_out generates Go validation files for our messages types, if specified.\n#\n# The lines starting with Mgoogle/... are proto import replacements,\n# which cause the generated file to import the specified packages\n# instead of the go_package's declared by the imported protof files.\n#\n\nDOCKER=docker\nDOCKER_PROTOBUF_VERSION=0.5.0\nDOCKER_PROTOBUF=jaegertracing/protobuf:$(DOCKER_PROTOBUF_VERSION)\nPROTOC := ${DOCKER} run --rm -u ${shell id -u} -v${PWD}:${PWD} -w${PWD} ${DOCKER_PROTOBUF} --proto_path=${PWD}\n\nPROTO_GEN=internal/proto-gen\nPATCHED_OTEL_PROTO_DIR = $(PROTO_GEN)/.patched-otel-proto\n\nPROTO_INCLUDES := \\\n\t-Iidl/proto/api_v2 \\\n\t-Iinternal/proto/metrics \\\n\t-I/usr/include/github.com/gogo/protobuf \\\n\t-Iidl/opentelemetry-proto\n\n# Remapping of std types to gogo types (must not contain spaces)\nPROTO_GOGO_MAPPINGS := $(shell echo \\\n\t\tMgoogle/protobuf/descriptor.proto=github.com/gogo/protobuf/types \\\n\t\tMgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types \\\n\t\tMgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types \\\n\t\tMgoogle/protobuf/empty.proto=github.com/gogo/protobuf/types \\\n\t\tMgoogle/api/annotations.proto=github.com/gogo/googleapis/google/api \\\n\t\tMmodel.proto=github.com/jaegertracing/jaeger-idl/model/v1 \\\n\t| $(SED) 's/  */,/g')\n\nOPENMETRICS_PROTO_FILES=$(wildcard internal/proto/metrics/*.proto)\n\n# The source directory for OTLP Protobufs from the sub-sub-module.\nOTEL_PROTO_SRC_DIR=idl/opentelemetry-proto/opentelemetry/proto\n\n# Find all OTEL .proto files, remove leading path (only keep relevant namespace dirs).\nOTEL_PROTO_FILES=$(subst $(OTEL_PROTO_SRC_DIR)/,,\\\n   $(shell ls $(OTEL_PROTO_SRC_DIR)/{common,resource,trace}/v1/*.proto))\n\n# Macro to execute a command passed as argument.\n# DO NOT DELETE EMPTY LINE at the end of the macro, it's required to separate commands.\ndefine exec-command\n$(1)\n\nendef\n\n# DO NOT DELETE EMPTY LINE at the end of the macro, it's required to separate commands.\ndefine print_caption\n  @echo \"🏗️ \"\n  @echo \"🏗️ \" $1\n  @echo \"🏗️ \"\n\nendef\n\n# Macro to compile Protobuf $(2) into directory $(1). $(3) can provide additional flags.\n# DO NOT DELETE EMPTY LINE at the end of the macro, it's required to separate commands.\n# Arguments:\n#  $(1) - output directory\n#  $(2) - path to the .proto file\n#  $(3) - additional flags to pass to protoc, e.g. extra -Ixxx\n#  $(4) - additional options to pass to gogo plugin\ndefine proto_compile\n  $(call print_caption, \"Processing $(2) --> $(1)\")\n\n  $(PROTOC) \\\n    $(PROTO_INCLUDES) \\\n    --gogo_out=plugins=grpc,$(strip $(4)),$(PROTO_GOGO_MAPPINGS):$(PWD)/$(strip $(1)) \\\n    $(3) $(2)\n\nendef\n\n.PHONY: proto\nproto: \\\n\tproto-storage-v2 \\\n\tproto-hotrod \\\n\tproto-zipkin \\\n\tproto-openmetrics \\\n\tproto-api-v3\n\n\nAPI_V2_PATCHED_DIR=$(PROTO_GEN)/.patched/api_v2\n.PHONY: patch-api-v2\npatch-api-v2:\n\tmkdir -p $(API_V2_PATCHED_DIR)\n\tcp idl/proto/api_v2/collector.proto $(API_V2_PATCHED_DIR)/\n\tcp idl/proto/api_v2/sampling.proto $(API_V2_PATCHED_DIR)/\n\tcat idl/proto/api_v2/query.proto | $(SED) 's|jaegertracing/jaeger-idl/model/v1.|jaegertracing/jaeger/model.|g' > $(API_V2_PATCHED_DIR)/query.proto\n\n\n.PHONY: proto-openmetrics\nproto-openmetrics:\n\t$(call print_caption, Processing OpenMetrics Protos)\n\t$(foreach file,$(OPENMETRICS_PROTO_FILES),$(call proto_compile, $(PROTO_GEN)/api_v2/metrics, $(file)))\n\nSTORAGE_V2_PATH=$(PROTO_GEN)/storage/v2\nSTORAGE_V2_PATCHED_DIR=$(PROTO_GEN)/.patched/storage_v2\nSTORAGE_V2_PATCHED_TRACE=$(STORAGE_V2_PATCHED_DIR)/trace_storage.proto\nSTORAGE_V2_PATCHED_DEPENDENCY=$(STORAGE_V2_PATCHED_DIR)/dependency_storage.proto\n\n.PHONY: patch-storage-v2\npatch-storage-v2:\n\tmkdir -p $(STORAGE_V2_PATCHED_DIR)\n\tcat idl/proto/storage/v2/trace_storage.proto | \\\n\t\t$(SED) -f ./$(PROTO_GEN)/patch.sed \\\n\t\t> $(STORAGE_V2_PATCHED_TRACE)\n\tcat idl/proto/storage/v2/dependency_storage.proto | \\\n\t\t$(SED) -f ./$(PROTO_GEN)/patch.sed \\\n\t\t> $(STORAGE_V2_PATCHED_DEPENDENCY)\n\n.PHONY: proto-storage-v2\nproto-storage-v2: patch-storage-v2\n\t$(call proto_compile, $(STORAGE_V2_PATH), $(STORAGE_V2_PATCHED_TRACE), -I$(STORAGE_V2_PATCHED_DIR) -Iinternal/storage/v2/grpc/)\n\t$(call proto_compile, $(STORAGE_V2_PATH), $(STORAGE_V2_PATCHED_DEPENDENCY), -I$(STORAGE_V2_PATCHED_DIR) -Iinternal/storage/v2/grpc/)\n\t@echo \"🏗️  replace first instance of OTEL import with internal type\"\n\t$(SED) -i '0,/go.opentelemetry.io\\/proto\\/otlp\\/trace\\/v1/s|go.opentelemetry.io/proto/otlp/trace/v1|github.com/jaegertracing/jaeger/internal/jptrace|' $(STORAGE_V2_PATH)/*.pb.go\n\t@echo \"🏗️  remove all remaining OTEL imports because we're not using any other OTLP types\"\n\t$(SED) -i 's+^.*v1 \"go.opentelemetry.io/proto/otlp/trace/v1\".*$$++' $(STORAGE_V2_PATH)/*.pb.go\n\n.PHONY: proto-hotrod\nproto-hotrod:\n\t$(call proto_compile, , examples/hotrod/services/driver/driver.proto)\n\n.PHONY: proto-zipkin\nproto-zipkin:\n\t$(call proto_compile, $(PROTO_GEN)/zipkin, idl/proto/zipkin.proto, -Iidl/proto)\n\n# The API v3 service uses official OTEL type opentelemetry.proto.trace.v1.TracesData,\n# which at runtime is mapped to a custom type in cmd/jaeger/internal/extension/jaegerquery/internal/internal/api_v3/traces.go\n# Unfortunately, gogoproto.customtype annotation cannot be applied to a method's return type,\n# only to fields in a struct, so we use regex search/replace to swap it.\n# Note that the .pb.go types must be generated into the same internal package $(API_V3_PATH)\n# where a manually defined traces.go file is located.\nAPI_V3_PATH=internal/proto/api_v3\nAPI_V3_PATCHED_DIR=$(PROTO_GEN)/.patched/api_v3\nAPI_V3_PATCHED=$(API_V3_PATCHED_DIR)/query_service.proto\n.PHONY: patch-api-v3\npatch-api-v3:\n\tmkdir -p $(API_V3_PATCHED_DIR)\n\tcat idl/proto/api_v3/query_service.proto | \\\n\t\t$(SED) -f ./$(PROTO_GEN)/patch.sed \\\n\t\t> $(API_V3_PATCHED)\n\n.PHONY: proto-api-v3\nproto-api-v3: patch-api-v3\n\t$(call proto_compile, $(API_V3_PATH), $(API_V3_PATCHED), -I$(API_V3_PATCHED_DIR) -Iidl/opentelemetry-proto)\n\t@echo \"🏗️  replace first instance of OTEL import with internal type\"\n\t$(SED) -i '0,/go.opentelemetry.io\\/proto\\/otlp\\/trace\\/v1/s|go.opentelemetry.io/proto/otlp/trace/v1|github.com/jaegertracing/jaeger/internal/jptrace|' $(API_V3_PATH)/query_service.pb.go\n\t@echo \"🏗️  remove all remaining OTEL imports because we're not using any other OTLP types\"\n\t$(SED) -i 's+^.*v1 \"go.opentelemetry.io/proto/otlp/trace/v1\".*$$++' $(API_V3_PATH)/query_service.pb.go\n"
  },
  {
    "path": "scripts/makefiles/Tools.mk",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# Copyright The OpenTelemetry Authors\n# SPDX-License-Identifier: Apache-2.0\n\nTOOLS_MOD_DIR   := $(SRC_ROOT)/internal/tools\nTOOLS_BIN_DIR   := $(SRC_ROOT)/.tools\nTOOLS_MOD_REGEX := \"\\s+_\\s+\\\".*\\\"\"\nTOOLS_PKG_NAMES := $(shell grep -E $(TOOLS_MOD_REGEX) < $(TOOLS_MOD_DIR)/tools.go | tr -d \" _\\\"\")\nTOOLS_BIN_NAMES := $(addprefix $(TOOLS_BIN_DIR)/, $(notdir $(shell echo $(TOOLS_PKG_NAMES) | sed 's|/v[0-9]||g')))\n\nGOFUMPT       := $(TOOLS_BIN_DIR)/gofumpt\nGOVERSIONINFO := $(TOOLS_BIN_DIR)/goversioninfo\nGOVULNCHECK   := $(TOOLS_BIN_DIR)/govulncheck\nLINT          := $(TOOLS_BIN_DIR)/golangci-lint\nMOCKERY       := $(TOOLS_BIN_DIR)/mockery\nSCHEMAGEN     := $(TOOLS_BIN_DIR)/schemagen\nGOCOVMERGE    := $(TOOLS_BIN_DIR)/gocovmerge\n\n# this target is useful for setting up local workspace, but from CI we want to call more specific ones\n.PHONY: install-tools\ninstall-tools: $(TOOLS_BIN_NAMES)\n\n.PHONY: install-test-tools\ninstall-test-tools: $(LINT) $(GOFUMPT)\n\n.PHONY: install-coverage-tools\ninstall-coverage-tools: $(GOCOVMERGE)\n\n.PHONY: install-ci\ninstall-ci: install-test-tools\n\nlist-internal-tools:\n\t@echo Third party tool modules:\n\t@echo $(TOOLS_PKG_NAMES) | tr ' ' '\\n' | sed 's/^/- /g'\n\t@echo Third party tool binaries:\n\t@echo $(TOOLS_BIN_NAMES) | tr ' ' '\\n' | sed 's/^/- /g'\n\n$(TOOLS_BIN_DIR):\n\tmkdir -p $@\n\n$(TOOLS_BIN_NAMES): $(TOOLS_BIN_DIR) $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum\n\tcd $(TOOLS_MOD_DIR) && $(GO) build -o $@ -trimpath $(shell echo $(TOOLS_PKG_NAMES) | tr ' ' '\\n' | grep $(notdir $@))\n"
  },
  {
    "path": "scripts/makefiles/Windows.mk",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# Copyright The OpenTelemetry Authors\n# SPDX-License-Identifier: Apache-2.0\n\nSYSOFILE=resource.syso\n\n# Magic values:\n# - LangID \"0409\" is \"US-English\".\n# - CharsetID \"04B0\" translates to decimal 1200 for \"Unicode\".\n# - FileOS \"040004\" defines the Windows kernel \"Windows NT\".\n# - FileType \"01\" is \"Application\".\n# https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource\ndefine VERSIONINFO\n{\n    \"FixedFileInfo\": {\n        \"FileVersion\": {\n            \"Major\": $(SEMVER_MAJOR),\n            \"Minor\": $(SEMVER_MINOR),\n            \"Patch\": $(SEMVER_PATCH),\n            \"Build\": 0\n        },\n        \"ProductVersion\": {\n            \"Major\": $(SEMVER_MAJOR),\n            \"Minor\": $(SEMVER_MINOR),\n            \"Patch\": $(SEMVER_PATCH),\n            \"Build\": 0\n        },\n        \"FileFlagsMask\": \"3f\",\n        \"FileFlags \": \"00\",\n        \"FileOS\": \"040004\",\n        \"FileType\": \"01\",\n        \"FileSubType\": \"00\"\n    },\n    \"StringFileInfo\": {\n        \"FileDescription\": \"$(NAME)\",\n        \"FileVersion\": \"$(SEMVER_MAJOR).$(SEMVER_MINOR).$(SEMVER_PATCH).0\",\n        \"LegalCopyright\": \"2015-2024 The Jaeger Project Authors\",\n\t\t\"ProductName\": \"$(NAME)\",\n        \"ProductVersion\": \"$(SEMVER_MAJOR).$(SEMVER_MINOR).$(SEMVER_PATCH).0\"\n    },\n    \"VarFileInfo\": {\n        \"Translation\": {\n            \"LangID\": \"0409\",\n            \"CharsetID\": \"04B0\"\n        }\n    }\n}\nendef\n\nexport VERSIONINFO\n\n.PHONY: _build_syso_once\n_build_syso_once:\n\techo $$VERSIONINFO\n\techo $$VERSIONINFO | $(GOVERSIONINFO) -64 -o=\"$(PKGPATH)/$(SYSOFILE)\" -\n\ndefine _build_syso_macro\n\t$(MAKE) _build_syso_once NAME=\"$(1)\" PKGPATH=\"$(2)\" SEMVER_MAJOR=$(SEMVER_MAJOR) SEMVER_MINOR=$(SEMVER_MINOR) SEMVER_PATCH=$(SEMVER_PATCH)\nendef\n\n.PHONY: _build-syso\n_build-syso: $(GOVERSIONINFO)\n\t$(eval SEMVER_ALL := $(shell scripts/utils/compute-version.sh -s))\n\t$(eval SEMVER_MAJOR := $(word 2, $(SEMVER_ALL)))\n\t$(eval SEMVER_MINOR := $(word 3, $(SEMVER_ALL)))\n\t$(eval SEMVER_PATCH := $(word 4, $(SEMVER_ALL)))\n\t$(call _build_syso_macro,Jaeger,cmd/jaeger)\n\t$(call _build_syso_macro,Jaeger Remote Storage,cmd/remote-storage)\n\t$(call _build_syso_macro,Jaeger Tracegen,cmd/tracegen)\n\t$(call _build_syso_macro,Jaeger Anonymizer,cmd/anonymizer)\n\t$(call _build_syso_macro,Jaeger ES-Index-Cleaner,cmd/es-index-cleaner)\n\t$(call _build_syso_macro,Jaeger ES-Rollover,cmd/es-rollover)\n\n.PHONY: _clean-syso\n_clean-syso:\n\trm ./cmd/*/$(SYSOFILE)\n"
  },
  {
    "path": "scripts/release/draft.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport argparse\nimport re\nimport subprocess\n\nversion_pattern = re.compile(r\"^[# ]*v?(\\d+\\.\\d+\\.\\d+)\")\n\nunderline_pattern = re.compile(r\"^[-]+$\", flags=0)\n\n\ndef main(title, repo, dry_run=False):\n    changelog_text, version = get_changelog(repo)\n    header = f\"{title} v{version}\"\n    tag = f\"v{version}\"\n    full_repo = f\"jaegertracing/{repo}\"\n\n    if dry_run:\n        print(\"Dry run: skipping release creation.\")\n        print(f\"Repository: {full_repo}\")\n        print(f\"Tag:        {tag}\")\n        print(f\"Title:      {header}\")\n        print(\"Changelog:\")\n        print(\"-\" * 20)\n        print(changelog_text)\n        print(\"-\" * 20)\n        return\n\n    print(changelog_text)\n    output_string = subprocess.check_output(\n        [\n            \"gh\",\n            \"release\",\n            \"create\",\n            tag,\n            \"--draft\",\n            \"--title\",\n            header,\n            \"--repo\",\n            full_repo,\n            \"-F\",\n            \"-\",\n        ],\n        input=changelog_text,\n        text=True,\n    )\n    print(f\"Draft created at: {output_string}\")\n    print(\"Please review, then edit it and click 'Publish release'.\")\n\n\ndef get_changelog(repo):\n# ... (rest of get_changelog remains the same)\n    changelog_text = \"\"\n    in_changelog_text = False\n    version = \"\"\n    with open(\"CHANGELOG.md\") as f:\n        for line in f:\n            versions = version_pattern.findall(line)\n\n            if versions:\n                # Found the first release headers.\n                if in_changelog_text:\n                    # Found the next release.\n                    break\n                else:\n                    # If both v1 and v2 are present, pick v2 (usually the last one).\n                    # If only one version is present, pick it.\n                    version = versions[-1]\n                    in_changelog_text = True\n            else:\n                underline_match = underline_pattern.match(line)\n                if underline_match is not None:\n                    continue\n                elif in_changelog_text:\n                    changelog_text += line\n\n    return changelog_text, version\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\n        description=\"List changes based on git log for release notes.\"\n    )\n\n    parser.add_argument(\n        \"--title\",\n        type=str,\n        default=\"Release\",\n        help=\"The title of the release. (default: Release)\",\n    )\n    parser.add_argument(\n        \"--repo\",\n        type=str,\n        default=\"jaeger\",\n        help=\"The repository name where the draft release will be created. (default: jaeger)\",\n    )\n    parser.add_argument(\n        \"-d\",\n        \"--dry-run\",\n        action=\"store_true\",\n        help=\"Print the release details without creating it.\",\n    )\n\n    args = parser.parse_args()\n\n    main(args.title, args.repo, args.dry_run)\n"
  },
  {
    "path": "scripts/release/formatter.py",
    "content": "#!/usr/bin/env python3\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport re\nimport sys\n\ndef extract_section_from_file(file_path, start_marker, end_marker):\n    with open(file_path, 'r') as f:\n        text = f.read()\n    start_index = text.find(start_marker)\n    if start_index == -1:\n        raise Exception(f\"start marker {start_marker!r} not found\")\n    start_index += len(start_marker)\n    end_index = text.find(end_marker)\n    if end_index == -1:\n        raise Exception(f\"end marker {end_marker!r} not found\")\n    return text[start_index:end_index]\n\ndef replace_star(text):\n    re_star = re.compile(r'(\\n\\s*)(\\*)(\\s)')\n    text = re_star.sub(r'\\1\\2 [ ]\\3', text)\n    return text\n\ndef replace_dash(text):\n    re_dash = re.compile(r'(\\n\\s*)(\\-)')\n    text = re_dash.sub(r'\\1* [ ]', text)\n    return text\n\ndef replace_num(text):\n    re_num = re.compile(r'(\\n\\s*)([0-9]*\\.)(\\s)')\n    text = re_num.sub(r'\\1* [ ]\\3', text)\n    return text\n\ndef replace_version(ui_text, backend_text, doc_text, pattern, ver):\n    ui_text = re.sub(pattern, ver, ui_text)\n    backend_text = re.sub(pattern, ver, backend_text)\n    doc_text = re.sub(pattern, ver, doc_text)\n    return ui_text, backend_text, doc_text\n\ndef fetch_content(file_name):\n    start_marker = \"<!-- BEGIN_CHECKLIST -->\"\n    end_marker = \"<!-- END_CHECKLIST -->\"\n    text = extract_section_from_file(file_name, start_marker, end_marker)\n    return text\n\ndef main():\n    \n    version = sys.argv[1]\n    ui_filename = sys.argv[2]\n    loc = sys.argv[3]\n\n    try:\n        backend_file_name = \"RELEASE.md\"\n        backend_section = fetch_content(backend_file_name)\n    except Exception as e:\n        sys.exit(f\"Failed to extract backendSection: {e}\")\n    backend_section = replace_star(backend_section)\n    backend_section = replace_num(backend_section)\n    try:\n        doc_filename = loc\n        doc_section = fetch_content(doc_filename)\n    except Exception as e:\n        sys.exit(f\"Failed to extract documentation section: {e}\")\n    doc_section=replace_dash(doc_section)\n\n    try:\n        ui_section = fetch_content(ui_filename)\n    except Exception as e:\n        sys.exit(f\"Failed to extract UI section: {e}\")\n\n    ui_section=replace_dash(ui_section)\n    ui_section=replace_num(ui_section)\n\n    # Concrete version - replace version patterns with the single version\n    version_pattern = r'(?:X\\.Y\\.Z|[0-9]+\\.[0-9]+\\.[0-9]+|[0-9]+\\.x\\.x)'\n    ui_section, backend_section, doc_section = replace_version(ui_section, backend_section, doc_section, version_pattern, version)\n\n    print(\"# UI Release\")\n    print(ui_section)\n    print(\"# Backend Release\")\n    print(backend_section)\n    print(\"# Doc Release\")\n    print(doc_section)\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/release/notes.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# This script can read N latest commits from one of Jaeger repos\n# and output them in the release notes format:\n# * {title} ({author} in {pull_request})\n#\n# Requires personal GitHub token with default permissions:\n#   https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token\n#\n# Usage: ./release-notes.py --help\n#\n\nimport argparse\nimport json\nimport os.path\nimport urllib.parse\nfrom os.path import expanduser\nimport sys\nfrom urllib.request import (\n    urlopen,\n    Request\n)\nfrom urllib.error import HTTPError\n\n\ndef eprint(*args, **kwargs):\n    print(*args, file=sys.stderr, **kwargs)\n\n\ndef print_token_error():\n    \"\"\"Print error message about GitHub token requirements.\"\"\"\n    generate_token_url = \"https://github.com/settings/tokens/new?description=GitHub%20Changelog%20Generator%20token\"\n    eprint(\"\\nError: Missing, invalid, or unauthorized GitHub token.\")\n    eprint(\"\\nPlease ensure your GitHub token:\")\n    eprint(\"  1. Is valid and has not expired\")\n    eprint(\"  2. Has 'repo' permissions (required to access repository data)\")\n    eprint(f\"\\nTo generate a new token, visit: {generate_token_url}\")\n    eprint(\"Make sure to select the 'repo' scope when creating the token.\")\n    eprint(\"\\nPlace the token in your --token-file and protect it: chmod 0600 <file>\")\n    sys.exit(1)\n\n\ndef github_api_request(url, token, additional_headers=None):\n    \"\"\"Make a GitHub API request with error handling.\n\n    Args:\n        url: The API URL to request\n        token: GitHub personal access token\n        additional_headers: Optional dict of additional headers to add\n\n    Returns:\n        Parsed JSON response\n    \"\"\"\n    try:\n        req = Request(url)\n        req.add_header('Authorization', f'token {token}')\n        if additional_headers:\n            for header, value in additional_headers.items():\n                req.add_header(header, value)\n        return json.loads(urlopen(req).read())\n    except HTTPError as e:\n        if e.code == 401:\n            print_token_error()\n        raise\n\n\ndef num_commits_since_prev_tag(token, base_url, branch, verbose):\n    tags_url = f\"{base_url}/tags\"\n    tags = github_api_request(tags_url, token)\n    prev_release_tag = tags[0]['name']\n    compare_url = f\"{base_url}/compare/{branch}...{prev_release_tag}\"\n    compare_results = github_api_request(compare_url, token)\n    num_commits = compare_results['behind_by']\n\n    if verbose:\n        eprint(f\"There are {num_commits} new commits since {prev_release_tag}\")\n    return num_commits\n\nUNCATTEGORIZED = 'Uncategorized'\ncategories = [\n    {'title': '#### ⛔ Breaking Changes', 'label': 'changelog:breaking-change'},\n    {'title': '#### ✨ New Features', 'label': 'changelog:new-feature'},\n    {'title': '#### 🐞 Bug fixes, Minor Improvements', 'label': 'changelog:bugfix-or-minor-feature'},\n    {'title': '#### 🚧 Experimental Features', 'label': 'changelog:experimental'},\n    {'title': '#### 👷 CI Improvements', 'label': 'changelog:ci'},\n    {'title': '#### ⚙️ Refactoring', 'label': 'changelog:refactoring'},\n    {'title': '#### 📖 Documentation', 'label': 'changelog:documentation'},\n    {'title': None, 'label': 'changelog:test'},\n    {'title': None, 'label': 'changelog:skip'},\n    {'title': None, 'label': 'changelog:dependencies'},\n]\n\n\ndef updateProgress(iteration, total_iterations):\n    progress = (iteration + 1) / total_iterations\n    percentage = progress * 100\n    sys.stderr.write('\\r[' + '='*int(progress*50) + ' '*(50-int(progress*50)) + f'] {percentage:.2f}%')\n    sys.stderr.flush()\n    if iteration >= total_iterations - 1:\n        eprint()\n    return iteration + 1\n\ndef main(token, repo, branch, num_commits, exclude_dependabot, verbose):\n    accept_header = \"application/vnd.github.groot-preview+json\"\n    base_url = f\"https://api.github.com/repos/jaegertracing/{repo}\"\n    commits_url = f\"{base_url}/commits\"\n    skipped_dependabot = 0\n\n    # If num_commits isn't set, get the number of commits made since the previous release tag.\n    if not num_commits:\n        num_commits = num_commits_since_prev_tag(token, base_url, branch, verbose)\n\n    if not num_commits:\n        return\n\n    # Load commits\n    data = urllib.parse.urlencode({'per_page': num_commits})\n    commits = github_api_request(commits_url + '?' + data, token)\n\n    if verbose:\n        eprint(req.full_url)\n        eprint('Retrieved', len(commits), 'commits')\n\n    # Load PR for each commit and print summary\n    category_results = {category['title']: [] for category in categories}\n    other_results = []\n    commits_with_multiple_labels = []\n\n    progress_iterator = 0\n    for commit in commits:\n        if verbose:\n            # Update the progress bar\n            progress_iterator = updateProgress(progress_iterator, num_commits)\n\n        sha = commit['sha']\n        author = commit['author']\n        author_login = author['login'] if author else 'unknown'\n        author_url = commit['author']['html_url'] if author else ''\n\n        if exclude_dependabot and author == \"dependabot[bot]\":\n            skipped_dependabot += 1\n            continue\n\n        msg_lines = commit['commit']['message'].split('\\n')\n        msg = msg_lines[0].capitalize()\n        pulls = github_api_request(f\"{commits_url}/{sha}/pulls\", token, {'accept': accept_header})\n        if len(pulls) > 1:\n            print(f\"WARNING: More than one pull request for commit {sha}\")\n\n        # Handle commits without pull requests.\n        if not pulls:\n            short_sha = sha[:7]\n            commit_url = commit['html_url']\n\n            result = f'* {msg} ([@{author_login}]({author_url}) in [{short_sha}]({commit_url}))'\n            other_results.append(result)\n            continue\n\n        pull = pulls[0]\n        pull_id = pull['number']\n        pull_url = pull['html_url']\n        msg = msg.replace(f'(#{pull_id})', '').strip()\n\n        # Check if the pull request has changelog label\n        pull_labels = get_pull_request_labels(token, repo, pull_id)\n        changelog_labels = [label for label in pull_labels if label.startswith('changelog:')]\n\n        # Handle multiple changelog labels\n        if len(changelog_labels) > 1:\n            commits_with_multiple_labels.append((sha, pull_id, changelog_labels))\n            continue\n\n        category = UNCATTEGORIZED\n        if changelog_labels:\n            for cat in categories:\n                if changelog_labels[0].startswith(cat['label']):\n                    category = cat['title']\n                    break\n\n        result = f'* {msg} ([@{author_login}]({author_url}) in [#{pull_id}]({pull_url}))'\n        if category == UNCATTEGORIZED:\n            other_results.append(result)\n        else:\n            category_results[category].append(result)\n\n    # Print categorized pull requests\n    if repo == 'jaeger':\n        print()\n        print('### Backend Changes')\n        print()\n\n    for category, results in category_results.items():\n        if results and category:\n            print(f'{category}\\n')\n            for result in results:\n                print(result)\n            print()\n\n    # Print pull requests in the 'UNCATTEGORIZED' category\n    if other_results:\n        print(f'### 💩💩💩 The following commits cannot be categorized (missing \"changelog:*\" labels):')\n        for result in other_results:\n            print(result)\n        print(f'### 💩💩💩 Please attach labels to these ^^^ PRs and rerun the script.')\n        print(f'### 💩💩💩 Do not include this section in the changelog.')\n\n    # Print warnings for commits with more than one changelog label\n    if commits_with_multiple_labels:\n        eprint(\"Warnings: Commits with more than one changelog label found. Please fix them:\\n\")\n        for sha, pull_id, labels in commits_with_multiple_labels:\n            pr_url = f\"https://github.com/jaegertracing/{repo}/pull/{pull_id}\"\n            eprint(f\"Commit {sha} associated with multiple changelog labels: {', '.join(labels)}\")\n            eprint(f\"Pull Request URL: {pr_url}\\n\")\n        print()\n\n    if skipped_dependabot:\n        if verbose:\n            eprint(f\"(Skipped dependabot commits: {skipped_dependabot})\")\n\n\ndef get_pull_request_labels(token, repo, pull_number):\n    labels_url = f\"https://api.github.com/repos/jaegertracing/{repo}/issues/{pull_number}/labels\"\n    labels = github_api_request(labels_url, token)\n    return [label['name'] for label in labels]\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description='List changes based on git log for release notes.')\n\n    parser.add_argument('--token-file', type=str, default=\"~/.github_token\",\n                        help='The file containing your personal github token to access the github API. ' +\n                             '(default: ~/.github_token)')\n    parser.add_argument('--repo', type=str, default='jaeger',\n                        help='The repository name to fetch commit logs from. (default: jaeger)')\n    parser.add_argument('--branch', type=str, default='main',\n                        help='The branch name to fetch commit logs from. (default: main)')\n    parser.add_argument('--exclude-dependabot', action='store_true',\n                        help='Excludes dependabot commits. (default: false)')\n    parser.add_argument('--num-commits', type=int,\n                        help='Print this number of commits from git log. ' +\n                             '(default: number of commits before the previous tag)')\n    parser.add_argument('--verbose', action='store_true',\n                        help='Whether output debug logs. (default: false)')\n\n    args = parser.parse_args()\n    token_file = expanduser(args.token_file)\n\n    if not os.path.exists(token_file):\n        eprint(f\"No such token-file: {token_file}.\")\n        print_token_error()\n\n    with open(token_file, 'r') as file:\n        token = file.read().replace('\\n', '')\n\n    if not token:\n        eprint(f\"{token_file} is missing your personal github token.\")\n        print_token_error()\n\n    main(token, args.repo, args.branch, args.num_commits, args.exclude_dependabot, args.verbose)\n"
  },
  {
    "path": "scripts/release/prepare.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# This script automates the Jaeger release preparation process.\n# It creates a pull request with all the changes necessary for release:\n#   1. Update CHANGELOG.md with the new version and auto-generated release notes.\n#   2. Update the jaeger-ui submodule to the corresponding version\n#   3. Rotate the release managers table in RELEASE.md\n#   4. Create a PR with the 'changelog:skip' label\n#   5. Include exact tag commands in the PR description for post-merge execution.\n#\n# Use:\n#   bash scripts/release/prepare.sh <version>\n#   OR\n#   make prepare-release VERSION=<version>\n#\n# Example:\n#   bash scripts/release/prepare.sh 2.14.0\n#   make prepare-release VERSION=2.14.0\n#\n# After the PR is merged, follow the tag commands in the PR description.\n\nset -euo pipefail\n\ncheck_prerequisites() {\n    for tool in gh git python3; do\n        if ! command -v \"$tool\" &> /dev/null; then\n            echo \"Error: $tool is not installed or not in PATH\"\n            exit 1\n        fi\n    done\n}\n\n# Verify we're on main branch\nverify_on_main_branch() {\n    local current_branch\n\n    current_branch=$(git rev-parse --abbrev-ref HEAD)\n    if [ \"$current_branch\" != \"main\" ]; then\n        echo \"Warning: Not on main branch (current: ${current_branch})\"\n        read -p \"Continue anyway? (y/n) \" -n 1 -r\n        echo\n        if [[ ! $REPLY =~ ^[Yy]$ ]]; then\n            exit 1\n        fi\n    fi\n}\n\n# find by checking URL patterns\nfetch_from_official_remote() {\n    local official_remote\n\n    if ! official_remote=$(bash scripts/utils/find-official-remote.sh); then\n        exit 1\n    fi\n\n    echo \"Fetching from official repo: $official_remote\"\n    git fetch \"$official_remote\"\n}\n\n# Create a new branch\ncreate_release_branch() {\n    local version=$1\n    local branch_name\n    branch_name=\"prepare-release-v${version}-$(date +%s)\"\n\n    git checkout -b \"${branch_name}\"\n    echo \"$branch_name\"\n}\n\n# Update UI submodule\nupdate_ui_submodule() {\n    local version=$1\n    local ui_version=\"v${version}\"\n\n    echo \"Updating UI submodule...\"\n    make init-submodules\n    # Verify if the directory exists and is not empty\n    if [ ! -d \"jaeger-ui\" ] || [ ! \"$(ls -A jaeger-ui)\" ]; then\n        echo \"Error: jaeger-ui directory does not exist or is empty\"\n        exit 1\n    fi\n\n    pushd jaeger-ui > /dev/null\n    git fetch origin\n    if git rev-parse \"${ui_version}\" >/dev/null 2>&1; then\n        git checkout \"${ui_version}\"\n        echo \"Checked out jaeger-ui ${ui_version}\"\n    else\n        # UI version not found\n        echo \"Warning: UI version ${ui_version} not found\"\n        read -r -p \"Enter UI version to use, e.g. ${ui_version} (or Enter to skip): \" ui_input\n        if [ -n \"$ui_input\" ]; then\n            git checkout \"$ui_input\"\n        else\n            echo \"Skipping UI version update\"\n            git checkout main && git pull\n        fi\n    fi\n\n    popd > /dev/null\n    git add jaeger-ui\n}\n\n# Generate changelog entries and update CHANGELOG.md\nupdate_changelog() {\n    local version=$1\n    local release_date\n    local changelog_content\n\n    echo \"Updating CHANGELOG.md...\"\n    release_date=$(date +%Y-%m-%d)\n    changelog_content=$(make -s changelog)\n\n    python3 scripts/release/update-changelog.py \"$version\" --date \"$release_date\" --content \"$changelog_content\" --ui-changelog jaeger-ui/CHANGELOG.md\n    git add CHANGELOG.md\n}\n\nrotate_release_managers() {\n    echo \"Rotating release managers table...\"\n    python3 scripts/release/rotate-managers.py\n    # Stage RELEASE.md if it was modified\n    git diff --quiet RELEASE.md || git add RELEASE.md\n}\n\ncommit_changes() {\n    local version=$1\n\n    git commit -s -m \"Prepare release v${version}\n\n- Updated CHANGELOG.md with release notes\n- Updated jaeger-ui submodule\n- Rotated release managers table\"\n}\n\npush_branch() {\n    local branch_name=$1\n    git push origin \"${branch_name}\"\n}\n\ncreate_pull_request() {\n    local version=$1\n\n    local pr_body=\"This PR prepares the release for v${version}.\n\n## Changes\n- [x] Updated CHANGELOG.md with release notes\n- [x] Updated jaeger-ui submodule to v${version}\n- [x] Rotated release managers table in RELEASE.md\n\nAfter this PR is merged, continue with the release process as outlined in the release issue.\"\n\n    # Create the PR\n    gh pr create \\\n        --title \"Prepare release v${version}\" \\\n        --body \"$pr_body\" \\\n        --label \"changelog:skip\" \\\n        --base main\n}\n\nmain() {\n    if [ \"$#\" -ne 1 ]; then\n        echo \"Usage: $0 <version>\"\n        echo \"Example: $0 2.14.0\"\n        exit 1\n    fi\n\n    local version=\"${1#v}\" # Remove 'v' prefix if present\n\n    echo \"Preparing release for v${version}\"\n\n    check_prerequisites\n    verify_on_main_branch\n    fetch_from_official_remote\n\n    local branch_name\n    branch_name=$(create_release_branch \"$version\")\n\n    update_ui_submodule \"$version\"\n    update_changelog \"$version\"\n    rotate_release_managers\n    commit_changes \"$version\"\n    push_branch \"$branch_name\"\n    create_pull_request \"$version\"\n\n    echo \"Done. Review and merge the PR, then follow the instructions in the PR description.\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "scripts/release/rotate-managers.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n\"\"\"\nThis script finds the release managers table in RELEASE.md and move the first data row to the end, rotating the release manager schedule.\n\"\"\"\n\nimport re\nimport sys\nfrom datetime import datetime, timedelta\n\n\ndef get_next_first_wednesday(last_date_str: str) -> str:\n    # last_date_str e.g. \"7 July 2026\"\n    last_date = datetime.strptime(last_date_str.strip(), \"%d %B %Y\")\n    # Move to the next month\n    if last_date.month == 12:\n        next_month = last_date.replace(year=last_date.year + 1, month=1, day=1)\n    else:\n        next_month = last_date.replace(month=last_date.month + 1, day=1)\n\n    # Find the first Wednesday\n    # weekday() is 0 for Monday, 2 for Wednesday\n    days_to_wednesday = (2 - next_month.weekday() + 7) % 7\n    first_wednesday = next_month + timedelta(days=days_to_wednesday)\n\n    return first_wednesday.strftime(\"%-d %B %Y\")\n\n\ndef rotate_release_managers() -> None:\n    with open('RELEASE.md', 'r') as f:\n        content = f.read()\n\n    # Find the release managers table\n    # Matches the header, separator, and all data rows.\n    # The last row might not have a trailing newline.\n    table_pattern = r'(\\| Version \\| Release Manager \\| Tentative release date *\\|\\n\\|-+\\|.*\\|\\n(?:\\|.*\\|.*\\|(?:\\n|$))*)'\n    match = re.search(table_pattern, content)\n\n    if not match:\n        print(\"Error: Could not find release managers table\", file=sys.stderr)\n        sys.exit(1)\n\n    table = match.group(0)\n    # Ensure we preserve the exact line endings by not stripping the match if we don't need to.\n    # But for rotation, we need the lines.\n    lines = table.splitlines()\n\n    # skip header and separator\n    header_plus_sep = lines[:2]\n    data_lines = lines[2:]\n\n    if not data_lines:\n        print(\"Error: No data lines found in release managers table\", file=sys.stderr)\n        sys.exit(1)\n\n    # Find the maximum version currently in the table to determine the next one\n    max_major, max_minor, max_patch = -1, -1, -1\n    last_date_str = \"\"\n\n    for line in data_lines:\n        # Split and filter to get clean parts\n        parts = [p.strip() for p in line.split('|') if p.strip()]\n        if len(parts) >= 3:\n            v_str = parts[0]\n            d_str = parts[2]\n            v_parts = v_str.split('.')\n            if len(v_parts) == 3:\n                try:\n                    major, minor, patch = map(int, v_parts)\n                    if (major, minor, patch) > (max_major, max_minor, max_patch):\n                        max_major, max_minor, max_patch = major, minor, patch\n                        last_date_str = d_str\n                except ValueError:\n                    continue\n\n    if max_major == -1:\n        print(\"Error: Could not find any valid versions in the table\", file=sys.stderr)\n        sys.exit(1)\n\n    next_version = f\"{max_major}.{max_minor + 1}.0\"\n    next_date = get_next_first_wednesday(last_date_str)\n\n    # Get the first row (the one to be rotated)\n    first_row = data_lines[0]\n    first_row_parts = [p.strip() for p in first_row.split('|') if p.strip()]\n    if len(first_row_parts) < 2:\n        print(f\"Error: First data row is malformed (expected at least 2 columns): {first_row}\", file=sys.stderr)\n        sys.exit(1)\n    manager = first_row_parts[1]\n\n    # Create the new row for the bottom\n    # Version (7) + Manager (15) + Date (25)\n    # Total width with spaces: (1+7+1) + (1+15+1) + (1+25+1) = 9 + 17 + 27 = 53 dashes/chars.\n    new_bottom_row = f\"| {next_version:<7} | {manager:<15} | {next_date:<25} |\"\n\n    # Move first line to the end (rotation)\n    rotated = data_lines[1:] + [new_bottom_row]\n    # Reconstruct the table with a trailing newline to avoid corruption when joining back\n    new_table = '\\n'.join(header_plus_sep + rotated) + '\\n'\n    content = content[:match.start()] + new_table + content[match.end():]\n\n    with open('RELEASE.md', 'w') as f:\n        f.write(content)\n    print(f\"Rotated release managers table. Added {next_version} for {manager} on {next_date}\")\n\ndef main():\n    rotate_release_managers()\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/release/start.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n#Requires bash version to be >=4. Will add alternative for lower versions\nset -euo pipefail\n\ndry_run=false\n\nwhile getopts \"dh\" opt; do\n    case \"${opt}\" in\n        d)\n            dry_run=true\n            ;;\n        h)\n            echo \"Usage: $0 [-d]\"\n            exit 0\n            ;;\n        *)\n            echo \"Usage: $0 [-d]\"\n            exit 1\n            ;;\n    esac\ndone\nif ! current_version=$(make \"echo-version\"); then\n  echo \"Error: Failed to fetch current version from make echo-version.\"\n  exit 1\nfi\n\n# removing the v so that in the line \"New version: v2.13.0\", v cannot be removed with backspace\nclean_version=\"${current_version#v}\" \n\nIFS='.' read -r major minor patch <<< \"$clean_version\"\n\nminor=$((minor + 1))\npatch=0\nsuggested_version=\"${major}.${minor}.${patch}\"\necho \"Current version: ${current_version}\"\nread -r -e -p \"New version: v\" -i \"${suggested_version}\" user_version\n\nnew_version=\"v${user_version}\"\necho \"Using new version: ${new_version}\"\n\n\n\nDOCS_TEMPLATE=$(mktemp \"/tmp/DOC_RELEASE.XXXXXX\")\nwget -O \"$DOCS_TEMPLATE\" https://raw.githubusercontent.com/jaegertracing/documentation/main/RELEASE.md\n\nUI_TMPFILE=$(mktemp \"/tmp/UI_RELEASE.XXXXXX\")\nwget -O \"$UI_TMPFILE\" https://raw.githubusercontent.com/jaegertracing/jaeger-ui/main/RELEASE.md\n\nissue_body=$(python3 scripts/release/formatter.py \"${user_version}\" \"${UI_TMPFILE}\" \"${DOCS_TEMPLATE}\")\n\nif $dry_run; then\n  echo \"${issue_body}\"\nelse\n  gh issue create -R jaegertracing/jaeger --title \"Prepare Jaeger Release ${new_version}\" --body \"$issue_body\"\nfi\n\nrm \"${DOCS_TEMPLATE}\" \"${UI_TMPFILE}\"\n"
  },
  {
    "path": "scripts/release/update-changelog.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n\"\"\"\nThis script inserts a new release section into CHANGELOG.md with the provided\nversion, date, and changelog content. If no content is provided, it will insert the\nplaceholder text.\n\"\"\"\n\nimport argparse\nimport os\nimport sys\n\n\ndef extract_version_content(changelog_path: str, version: str) -> str:\n    \"\"\"\n    Extracts the content of a specific version section from a changelog file.\n    \"\"\"\n    if not os.path.exists(changelog_path):\n        return \"\"\n\n    with open(changelog_path, 'r') as f:\n        lines = f.readlines()\n\n    start_line = -1\n    v_header = f\"## v{version}\"\n    for i, line in enumerate(lines):\n        if line.startswith(v_header):\n            start_line = i + 1\n            break\n\n    if start_line == -1:\n        return \"\"\n\n    content = []\n    for i in range(start_line, len(lines)):\n        if lines[i].startswith('## v'):\n            break\n        content.append(lines[i])\n\n    return ''.join(content).strip()\n\n\ndef update_changelog(version: str, release_date: str, changelog_content: str, ui_changelog: str = None) -> None:\n    with open('CHANGELOG.md', 'r') as f:\n        lines = f.readlines()\n\n    # Find the template section end\n    template_end = -1\n    for i, line in enumerate(lines):\n        if '</details>' in line:\n            template_end = i + 1\n            break\n    \n    if template_end == -1:\n        print(\"Error: Could not find template end marker\", file=sys.stderr)\n        sys.exit(1)\n    \n    # Create the new changelog section\n    new_section = []\n    new_section.append(f\"\\nv{version} ({release_date})\\n\")\n    new_section.append(\"-\" * 31 + \"\\n\")\n    if not changelog_content.startswith('\\n'):\n        new_section.append(\"\\n\")\n    new_section.append(changelog_content)\n    if not changelog_content.endswith('\\n'):\n        new_section.append(\"\\n\")\n    \n    if ui_changelog:\n        ui_content = extract_version_content(ui_changelog, version)\n        if ui_content:\n            new_section.append(\"\\n### 📊 UI Changes\\n\\n\")\n            new_section.append(ui_content)\n            new_section.append(\"\\n\")\n\n    with open('CHANGELOG.md', 'w') as f: # Write the updated CHANGELOG.md\n        f.writelines(lines[:template_end])\n        f.writelines(new_section)\n        f.writelines(lines[template_end:])\n    \n    print(f\"Updated CHANGELOG.md with v{version}\")\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Update CHANGELOG.md with a new version section.\"\n    )\n    parser.add_argument(\n        \"version\",\n        type=str,\n        help=\"Version number (e.g., 2.14.0)\"\n    )\n    parser.add_argument(\n        \"--date\",\n        type=str,\n        help=\"Release date in YYYY-MM-DD format (default: today)\",\n        default=None\n    )\n    parser.add_argument(\n        \"--content\",\n        type=str,\n        help=\"Changelog content (default: placeholder text)\",\n        default=None\n    )\n    parser.add_argument(\n        \"--ui-changelog\",\n        type=str,\n        help=\"Path to the UI changelog file to extract notes from\",\n        default=None\n    )\n    \n    args = parser.parse_args()\n    \n    # Use provided date or default to today\n    from datetime import date\n    release_date = args.date if args.date else date.today().strftime(\"%Y-%m-%d\")\n    \n    update_changelog(args.version, release_date, args.content, args.ui_changelog)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/utils/compare_metrics.py",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport json\nimport argparse\nimport subprocess\n\n#Instructions of use:\n\n# To generate V1_Metrics.json and V2_Metrics.json, run the following commands:\n# i.e for elastic search first run the following command:\n# docker compose -f docker-compose/elasticsearch/v7/docker-compose.yml up\n# 1. Generate V1_Metrics.json and V2_Metrics.json by the following commands:\n# V1 binary cmd: SPAN_STORAGE_TYPE=elasticsearch go run -tags=ui ./cmd/all-in-one\n# extract the metrics by running the following command:\n# prom2json http://localhost:14269/metrics > V1_Metrics.json\n# Stop the v1 binary and for v2 binary run the following command:\n# go run -tags ui ./cmd/jaeger/main.go --config ./cmd/jaeger/config-elasticsearch.yaml\n# extract the metrics by running the following command:\n# prom2json http://localhost:8888/metrics > V2_Metrics.json\n# it is first recomended to generate the differences for all-in-one.json by running the following command:\n# python3 compare_metrics.py --out md --is_storage F\n# rename that file to all_in_one.json and use it to filter out the overlapping metrics by using the is_storage falg to T\n# 2. Run the script with the following command:\n#    python3 compare_metrics.py --out {json or md} --is_storage {T or F}\n# 3. The script will compare the metrics in V1_Metrics.json and V2_Metrics.json and output the differences to differences.json\n\n\n# Extract names and labels of the metrics\ndef extract_metrics_with_labels(metrics, strip_prefix=None):\n    result = {}\n    for metric in metrics:\n        \n        name = metric['name']\n        print(name)\n        if strip_prefix and name.startswith(strip_prefix):\n            name = name[len(strip_prefix):]\n        labels = {}\n        if 'metrics' in metric and 'labels' in metric['metrics'][0]:\n            labels = metric['metrics'][0]['labels']\n        result[name] = labels\n    return result\n\ndef remove_overlapping_metrics(all_in_one_data, other_json_data):\n    \"\"\"Remove overlapping metrics found in all_in-one.json from another JSON.\"\"\"\n    # Loop through v1 and v2 metrics to remove overlaps\n    for metric_category in ['common_metrics', 'v1_only_metrics', 'v2_only_metrics']:\n        if metric_category in all_in_one_data and metric_category in other_json_data:\n            for metric in all_in_one_data[metric_category]:\n                if metric in other_json_data[metric_category]:\n                    del other_json_data[metric_category][metric]\n\n    return other_json_data\n\n\n# Your current compare_metrics.py logic goes here\ndef main():\n    parser = argparse.ArgumentParser(description='Compare metrics and output format.')\n    parser.add_argument('--out', choices=['json', 'md'], default='json',\n                        help='Output format: json (default) or md')\n    parser.add_argument('--is_storage', choices=['T','F'],default='F', help='Remove overlapping storage metrics')\n    # Parse the arguments\n    args = parser.parse_args()\n\n    # Call your existing compare logic here\n    print(\"Running metric comparison...\")\n    v1_metrics_path = \"\" #Add the path to the V1_Metrics.json file\n    v2_metrics_path = \"\" #Add the path to the V2_Metrics.json file      \n\n    with open(v1_metrics_path, 'r') as file:\n       v1_metrics = json.load(file)\n\n    with open(v2_metrics_path, 'r') as file:\n       v2_metrics = json.load(file)\n\n    v1_metrics_with_labels = extract_metrics_with_labels(v1_metrics)\n    v2_metrics_with_labels = extract_metrics_with_labels(\n       v2_metrics, strip_prefix=\"otelcol_\")\n\n    # Compare the metrics names and labels\n    common_metrics = {}\n    v1_only_metrics = {}\n    v2_only_metrics = {}\n\n    for name, labels in v1_metrics_with_labels.items():\n       if name in v2_metrics_with_labels:\n          common_metrics[name] = labels\n       elif not name.startswith(\"jaeger_agent\"):\n          v1_only_metrics[name] = labels\n\n    for name, labels in v2_metrics_with_labels.items():\n       if name not in v1_metrics_with_labels:\n          v2_only_metrics[name] = labels\n\n    differences = {\n      \"common_metrics\": common_metrics,\n      \"v1_only_metrics\": v1_only_metrics,\n      \"v2_only_metrics\": v2_only_metrics,\n    }\n\n    #Write the differences to a new JSON file\n    differences_path = \"./differences.json\"\n    with open(differences_path, 'w') as file:\n       json.dump(differences, file, indent=4)\n\n    print(f\"Differences written to {differences_path}\")\n    if args.is_storage == 'T':\n        all_in_one_path = \"\" #Add the path to the all_in_one.json file\n        with open(all_in_one_path, 'r') as file:\n            all_in_one_data = json.load(file)\n        with open(differences_path, 'r') as file:\n            other_json_data = json.load(file)\n        other_json_data = remove_overlapping_metrics(all_in_one_data, other_json_data)\n        with open(differences_path, 'w') as file:\n            json.dump(other_json_data, file, indent=4)\n        print(f\"Overlapping storage metrics removed from {differences_path}\")\n    # If the user requested markdown output, run metrics_md.py\n    if args.out == 'md':\n        try:\n            print(\"Running metrics_md.py to generate markdown output...\")\n            subprocess.run(['python3', 'metrics-md.py'], check=True)\n        except subprocess.CalledProcessError as e:\n            print(f\"Error running metrics_md.py: {e}\")\n    \n    # If json output is requested or no output type is provided (default is json)\n    else:\n        print(\"Output in JSON format.\")\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "scripts/utils/compute-tags.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Compute major/minor/etc image tags based on the current branch\n\nset -ef -o pipefail\n\nif [[ -z $QUIET ]]; then\n  set -x\nfi\n\nset -u\n\nBASE_BUILD_IMAGE=${1:?'expecting Docker image name as argument, such as jaegertracing/jaeger'}\nBRANCH=${BRANCH:?'expecting BRANCH env var'}\nGITHUB_SHA=${GITHUB_SHA:-$(git rev-parse HEAD)}\n\n# accumulate output in this variable\nIMAGE_TAGS=\"\"\n\n# append given tag for docker.io and quay.io\ntags() {\n    if [[ -n \"$IMAGE_TAGS\" ]]; then\n        # append space\n        IMAGE_TAGS=\"${IMAGE_TAGS} \"\n    fi\n    IMAGE_TAGS=\"${IMAGE_TAGS}--tag docker.io/${1} --tag quay.io/${1}\"\n}\n\n## If we are on a release tag, let's extract the version number.\n## The other possible values are 'main' or another branch name.\nif [[ $BRANCH =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?$ ]]; then\n    MAJOR_MINOR_PATCH=${BRANCH#v}\n    tags \"${BASE_BUILD_IMAGE}:${MAJOR_MINOR_PATCH}\"\n    tags \"${BASE_BUILD_IMAGE}:latest\"\nelif [[ $BRANCH != \"main\" ]]; then\n    # not on release tag nor on main - no tags are needed since we won't publish\n    echo \"\"\n    exit\nfi\n\ntags \"${BASE_BUILD_IMAGE}-snapshot:${GITHUB_SHA}\"\ntags \"${BASE_BUILD_IMAGE}-snapshot:latest\"\n\necho \"${IMAGE_TAGS}\"\n"
  },
  {
    "path": "scripts/utils/compute-tags.test.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# This script uses https://github.com/kward/shunit2 to run unit tests.\n# The path to this repo must be provided via SHUNIT2 env var.\n\nSHUNIT2=\"${SHUNIT2:?'expecting SHUNIT2 env var pointing to a dir with https://github.com/kward/shunit2 clone'}\"\n\n# allow substituting for ggrep, since default grep on MacOS doesn't grok -P flag.\n# if running on MacOS, `brew install grep` and run with GREP=ggrep\nGREP=${GREP:-grep}\n\n# shellcheck disable=SC2086\ncomputeTags=\"$(dirname $0)/compute-tags.sh\"\n\n# suppress command echoing by compute-tags.sh\nexport QUIET=1\n\n# unset env vars that were possibly set by the caller, since we test against them\nunset BRANCH\nunset GITHUB_SHA\n\ntestRequireImageName() {\n    err=$(bash \"$computeTags\" 2>&1)\n    assertContains \"$err\" 'expecting Docker image name'\n}\n\ntestRequireBranch() {\n    err=$(GITHUB_SHA=sha bash \"$computeTags\" foo/bar 2>&1)\n    assertContains \"$err\" \"$err\" 'expecting BRANCH env var'\n}\n\ntestGithubShaIsDefaulted() {\n    out=$(BRANCH=main bash \"$computeTags\" foo/bar)\n    expected=(\n        \"foo/bar-snapshot:$(git rev-parse HEAD)\"\n        \"foo/bar-snapshot:latest\"\n    )\n    expect \"${expected[@]}\"\n}\n\n# out is global var which is populated for every output under test\nout=\"\"\n\nscan_list() {\n    local target=\"$1\"\n    echo \"$out\" | tr ' ' '\\n' | $GREP -v '^--tag$' | $GREP -Po \"^($target)\"'$'\n}\n\nexpect_contains() {\n    local target=\"$1\"\n    # shellcheck disable=SC2155\n    local found=$(scan_list \"$target\")\n    assertContains \"$found\" \"$target\"\n}\n\nexpect_not_contains() {\n    local target=\"$1\"\n    # shellcheck disable=SC2155\n    local found=$(scan_list \"$target\")\n    assertNotContains \"$found\" \"$target\"\n}\n\nexpect() {\n    echo '   Actual:' \"$out\"\n    while [ \"$#\" -gt 0 ]; do\n        echo '   checking includes' \"$1\"\n        expect_contains \"docker.io/$1\"\n        expect_contains \"quay.io/$1\"\n        shift\n    done\n}\n\nexpect_not() {\n    echo '   Actual:' \"$out\"\n    while [ \"$#\" -gt 0 ]; do\n        echo '   checking excludes' \"$1\"\n        expect_not_contains \"docker.io/$1\"\n        expect_not_contains \"quay.io/$1\"\n        shift\n    done\n}\n\ntestRandomBranch() {\n    out=$(BRANCH=random GITHUB_SHA=sha bash \"$computeTags\" foo/bar)\n    expect\n}\n\ntestMainBranch() {\n    out=$(BRANCH=main GITHUB_SHA=sha bash \"$computeTags\" foo/bar)\n    expected=(\n        \"foo/bar-snapshot:sha\"\n        \"foo/bar-snapshot:latest\"\n    )\n    expect \"${expected[@]}\"\n    expect_not \"foo/bar\" \"foo/bar:latest\"\n}\n\ntestSemVerBranch() {\n    out=$(BRANCH=v1.2.3 GITHUB_SHA=sha bash \"$computeTags\" foo/bar)\n    expected=(\n        \"foo/bar:latest\"\n        \"foo/bar:1.2.3\"\n        \"foo/bar-snapshot:sha\"\n        \"foo/bar-snapshot:latest\"\n    )\n    expect \"${expected[@]}\"\n    expect_not \"foo/bar\"\n}\n\ntestSemVerRCBranch() {\n    out=$(BRANCH=v1.22.33-rc12 GITHUB_SHA=sha bash \"$computeTags\" foo/bar)\n    expected=(\n        \"foo/bar:latest\"\n        \"foo/bar:1.22.33-rc12\"\n        \"foo/bar-snapshot:sha\"\n        \"foo/bar-snapshot:latest\"\n    )\n    expect \"${expected[@]}\"\n    expect_not \"foo/bar\"\n}\n\n# shellcheck disable=SC1091\nsource \"${SHUNIT2}/shunit2\"\n"
  },
  {
    "path": "scripts/utils/compute-version.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# Extract and parse Jaeger release version from the closest Git tag.\n\nset -euf -o pipefail\nSED=${SED:-sed}\n\nusage() {\n  echo \"Usage: $0 [-s] [-v]\"\n  echo \"  -s  split semver into 4 parts: semver major minor patch\"\n  echo \"  -v  verbose\"\n  exit 1\n}\n\nverbose=\"false\"\nsplit=\"false\"\n\nwhile getopts \"sv\" opt; do\n\t# shellcheck disable=SC2220 # we don't need a *) case\n\tcase \"${opt}\" in\n\ts)\n\t\tsplit=\"true\"\n\t\t;;\n\tv)\n\t\tverbose=\"true\"\n\t\t;;\n\tesac\ndone\n\nshift $((OPTIND - 1))\n\n# Always use v2\nJAEGER_MAJOR=v2\n\nprint_result() {\n  if [[ \"$split\" == \"true\" ]]; then\n    echo \"$1\" \"$2\" \"$3\" \"$4\"\n  else\n    echo \"$1\"\n  fi\n}\n\nif [[ \"$verbose\" == \"true\" ]]; then\n  set -x\nfi\n\n# Some of GitHub Actions workflows do a shallow checkout without tags. This avoids logging warnings from git.\nif [[ $(git rev-parse --is-shallow-repository) == \"false\" ]]; then\n  GIT_CLOSEST_TAG=$(git describe --abbrev=0 --tags)\nelse\n  if [[ \"$verbose\" == \"true\" ]]; then\n    echo \"The repository is a shallow clone, cannot determine most recent tag\" >&2\n  fi\n  print_result 0.0.0 0 0 0\n  exit\nfi\n\nMATCHING_TAG=''\nfor tag in $(git tag --list --contains \"$(git rev-parse \"$GIT_CLOSEST_TAG\")\"); do\n  if [[ \"${tag:0:2}\" == \"$JAEGER_MAJOR\" ]]; then\n    MATCHING_TAG=\"$tag\"\n    break\n  fi\ndone\nif [[ \"$MATCHING_TAG\" == \"\" ]]; then\n  if [[ \"$verbose\" == \"true\" ]]; then\n    echo \"Did not find a tag matching major version $JAEGER_MAJOR\" >&2\n  fi\n  print_result 0.0.0 0 0 0\n  exit\nfi\n\nif [[ $MATCHING_TAG =~ ^v([0-9]+)\\.([0-9]+)\\.([0-9]+) ]]; then\n    MAJOR=\"${BASH_REMATCH[1]}\"\n    MINOR=\"${BASH_REMATCH[2]}\"\n    PATCH=\"${BASH_REMATCH[3]}\"\nelse\n    echo \"Invalid semver format: $MATCHING_TAG\"\n    exit 1\nfi\n\nprint_result \"$MATCHING_TAG\" \"$MAJOR\" \"$MINOR\" \"$PATCH\"\n"
  },
  {
    "path": "scripts/utils/docker-login.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -exu\n\nDOCKERHUB_USERNAME=${DOCKERHUB_USERNAME:-\"jaegertracingbot\"}\nDOCKERHUB_TOKEN=${DOCKERHUB_TOKEN:-}\nQUAY_USERNAME=${QUAY_USERNAME:-\"jaegertracing+github_workflows\"}\nQUAY_TOKEN=${QUAY_TOKEN:-}\n\necho \"Performing a 'docker login' for DockerHub\"\necho \"${DOCKERHUB_TOKEN}\" | docker login -u \"${DOCKERHUB_USERNAME}\" docker.io --password-stdin\n\necho \"Performing a 'docker login' for Quay\"\necho \"${QUAY_TOKEN}\" | docker login -u \"${QUAY_USERNAME}\" quay.io --password-stdin\n"
  },
  {
    "path": "scripts/utils/find-official-remote.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# finds the git remote pointing to the official jaeger repo.\nset -euo pipefail\n\nOFFICIAL_REPO_PATTERN='(github\\.com[:/])jaegertracing/jaeger(\\.git)?$'\n\nfor remote in $(git remote); do\n    remote_url=$(git remote get-url \"$remote\" 2>/dev/null || true)\n    if echo \"$remote_url\" | grep -Eq \"$OFFICIAL_REPO_PATTERN\"; then\n        echo \"$remote\"\n        exit 0\n    fi\ndone\n\necho \"Error: could not find a remote pointing to jaegertracing/jaeger\" >&2\necho \"Available remotes:\" >&2\ngit remote -v >&2\nexit 1"
  },
  {
    "path": "scripts/utils/generate-help-output.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n# This script runs the `help` command on all Jaeger binaries (using go run) with variations of SPAN_STORAGE_TYPE.\n# It can be used to compare the CLI API changes between releases.\n\ndir=$1\nif [[ \"$dir\" = \"\" ]]; then\n  echo specify output dir\n  exit 1\nfi\n\nfunction gen {\n  bin=$1\n  shift\n  for s in \"$@\"\n  do\n    SPAN_STORAGE_TYPE=$s go run \"./cmd/$bin\" help > \"$dir/$bin-$s.txt\"\n  done\n}\n\nset -ex\n\ngen collector  cassandra elasticsearch memory kafka badger grpc\ngen query      cassandra elasticsearch memory badger grpc\ngen ingester   cassandra elasticsearch memory badger grpc\ngen all-in-one cassandra elasticsearch memory badger grpc\n"
  },
  {
    "path": "scripts/utils/ids-to-base64.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport base64\nimport os\nimport re\nimport sys\n\n\ndef trace_id_base64(match):\n    id = int(match.group(1), 16)\n    hex = '%032x' % id\n    b64 = base64.b64encode(hex.decode('hex'))\n    return '\"traceId\": \"%s\"' % b64\n\n\ndef span_id_base64(match):\n    id = int(match.group(1), 16)\n    hex = '%016x' % id\n    b64 = base64.b64encode(hex.decode('hex'))\n    return f'\"spanId\": \"{b64}\"'\n\n\nfor file in sys.argv[1:]:\n    print(file)\n    backup = f'{file}.bak'\n    with open(file, 'r') as fin:\n        with open(backup, 'w') as fout:\n            for line in fin:\n                # line = line[:-1] # remove \\n\n                line = re.sub(r'\"traceId\": \"(.+)\"', trace_id_base64, line)\n                line = re.sub(r'\"spanId\": \"(.+)\"', span_id_base64, line)\n                fout.write(line)\n    os.remove(file)\n    os.rename(backup, file)\n"
  },
  {
    "path": "scripts/utils/metrics-md.py",
    "content": "# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nimport json\n\n\ndef generate_spans_markdown_table(v1_spans, v2_spans):\n    \"\"\"\n    Generates a markdown table specifically for spans metrics with two main columns V1 and V2.\n\n    Args:\n        v1_spans (dict): The dictionary of V1 spans metrics.\n        v2_spans (dict): The dictionary of V2 spans metrics.\n\n    Returns:\n        str: The generated markdown table as a string.\n    \"\"\"\n    table = \"### Equivalent Metrics\\n\\n\"\n    table += \"| V1 Metric | V1 Parameters | V2 Metric | V2 Parameters |\\n\"\n    table += \"|-----------|---------------|-----------|---------------|\\n\"\n\n\n    # Iterate through the metrics using zip_longest to handle mismatched lengths\n    from itertools import zip_longest\n\n    for (v1_metric, v1_params), (v2_metric, v2_params) in zip_longest(v1_spans.items(), v2_spans.items(), fillvalue=('', {})):\n        v1_inner_keys = ', '.join(v1_params.keys()) if v1_params else ''\n        v2_inner_keys = ', '.join(v2_params.keys()) if v2_params else ''\n        table += f\"| {v1_metric} | {v1_inner_keys} | {v2_metric} | {v2_inner_keys} |\\n\"\n\n    return table\n\n\n\ndef generate_combined_markdown_table(common_metrics, v1_metrics, v2_metrics):\n    \"\"\"\n    Generates a markdown table for combined metrics from common, V1, and V2.\n\n    Args:\n        common_metrics (dict): The dictionary of common metrics.\n        v1_metrics (dict): The dictionary of V1 only metrics.\n        v2_metrics (dict): The dictionary of V2 only metrics.\n\n    Returns:\n        str: The generated markdown table as a string.\n    \"\"\"\n    table = \"### Combined Metrics\\n\\n\"\n    table += \"| V1 Metric | V1 Parameters | V2 Metric | V2 Parameters |\\n\"\n    table += \"|-----------|---------------|-----------|---------------|\\n\"\n    for metric_name, params in common_metrics.items():\n        v1_params = ', '.join(common_metrics[metric_name].keys()) if params else 'N/A'\n        v2_params = ', '.join(common_metrics[metric_name].keys()) if params else 'N/A'\n        table += f\"| {metric_name} | {v1_params} | {metric_name} | {v2_params} |\\n\"\n\n    # Then, handle V1-only metrics (V2 shows as N/A)\n    for metric_name, v1_params in v1_metrics.items():\n            v1_params_str = ', '.join(v1_params.keys()) if v1_params else 'N/A'\n            table += f\"| {metric_name} | {v1_params_str} | N/A | N/A |\\n\"\n\n    # Then, handle V2-only metrics (V1 shows as N/A)\n    for metric_name, v2_params in v2_metrics.items():\n            v2_params_str = ', '.join(v2_params.keys()) if v2_params else 'N/A'\n            table += f\"| N/A | N/A | {metric_name} | {v2_params_str} |\\n\"\n\n    return table\n\nclass ConvertJson:\n\n    def __init__(self, json_fp, h1):\n        self.fp = json_fp\n        self.h1 = h1\n        self.jdata = self.get_json()\n        self.mddata = self.format_json_to_md()\n\n    def get_json(self):\n        with open(self.fp) as f:\n            res = json.load(f)\n        return res\n\n    def format_json_to_md(self):\n        text = f'# {self.h1}\\n'\n        dct = self.jdata\n\n        # Extracting individual metric dictionaries\n        common_metrics = dct.get(\"common_metrics\", {})\n        v1_only_metrics = dct.get(\"v1_only_metrics\", {})\n        v2_only_metrics = dct.get(\"v2_only_metrics\", {})\n\n        # Generate combined table\n        combined_metrics_table = generate_combined_markdown_table(\n            common_metrics, v1_only_metrics, v2_only_metrics\n        )\n        \n        filtered_v1_metrics = {\n       \"jaeger_collector_spans_rejected_total\": {\"debug\": \"false\", \"format\": \"\",\"svc\": \"\",\"transport\":\"\"},\n       \"jaeger_build_info\": {\"build_date\": \"\",\"revision\": \"\",\" version\": \"\"}  # Add more metrics as needed\n       }\n\n        # Hardcoding filtered v2 metrics\n        filtered_v2_metrics = {\n       \"receiver_refused_spans\": {\"receiver\": \"\",\"service_instance_id\": \"\",\"service_name\": \"\",\"service_version\": \"\",\"transport\": \"\"},\n       \"target_info\": {\"service_instance_id\": \"\",\"service_name\": \"\",\"service_version\": \"\"}  # Add more metrics as needed\n        }\n        spans_metrics_table = generate_spans_markdown_table(filtered_v1_metrics, filtered_v2_metrics)\n        text += combined_metrics_table+spans_metrics_table\n        return text\n\n    def convert_dict_to_md(self, output_fn):\n        with open(output_fn, 'w') as writer:\n            writer.writelines(self.mddata)\n        print('Dict successfully converted to md')\n\n# Usage\nfn = ''  # Enter the path of the JSON file generated by compare_metrics.py\ntitle = \"TITLE\"\nconverter = ConvertJson(fn, title)\nconverter.convert_dict_to_md(output_fn='metrics.md')"
  },
  {
    "path": "scripts/utils/platforms-to-gh-matrix.sh",
    "content": "#!/bin/bash\n#\n# Copyright (c) 2024 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\nset -euf -o pipefail\n\necho -n '{ \"include\": [ '\nfirst=\"true\"\nfor pair in $(make echo-platforms | tr ',' ' '); do\n  os=$(echo \"$pair\" | cut -d '/' -f 1)\n  arch=$(echo \"$pair\" | cut -d '/' -f 2)\n  if [[ \"$first\" == \"true\" ]]; then\n    first=\"false\"\n  else\n    echo -n ' ,'\n  fi\n  echo -n \"{ \\\"os\\\": \\\"$os\\\", \\\"arch\\\": \\\"$arch\\\" }\"\ndone\n\necho \"]}\"\n"
  },
  {
    "path": "scripts/utils/run-tests.sh",
    "content": "#!/bin/bash\n\n# Copyright (c) 2025 The Jaeger Authors.\n# SPDX-License-Identifier: Apache-2.0\n\n\nUTILS_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nREPO_ROOT=\"$UTILS_DIR/../..\"\n\n# Define list of test files explicitly here , to be dynamic to the location of the test file\nTEST_FILES=(\n    \"$UTILS_DIR/compute-tags.test.sh\"\n    \"$REPO_ROOT/internal/storage/v1/cassandra/schema/create.test.sh\"\n)\n\nrun_test_file() {\n    local test_file=\"$1\"\n    if [ ! -f \"$test_file\" ]; then\n        echo \"Error: Test file not found: $test_file\"\n        return 1\n    fi\n    \n    echo \"Running tests from: $test_file\"\n    \n    export SHUNIT2=\"${SHUNIT2:?'SHUNIT2 environment variable must be set'}\"\n\n    bash \"$test_file\"\n    local result=$?\n    echo \"Test file $test_file completed with status: $result\"\n    return $result\n}\n\nmain() {\n\n    if [ ! -f \"${SHUNIT2}/shunit2\" ]; then\n        echo \"Error: shunit2 not found at ${SHUNIT2}/shunit2\"\n        exit 1\n    fi\n    local failed=0\n    local total=0\n    local passed=0\n    local failed_tests=()\n\n    # Run all test files\n    for test_file in \"${TEST_FILES[@]}\"; do\n        ((total++))\n        if ! run_test_file \"$test_file\"; then\n            failed=1\n            failed_tests+=(\"$test_file\")\n        else\n            ((passed++))\n        fi\n    done\n\n    echo \"-------------------\"\n    echo \"Test Summary:\"\n    echo \"Total: $total\"\n    echo \"Passed: $passed\"\n    echo \"Failed: $((total - passed))\"\n    \n    if [ ${#failed_tests[@]} -gt 0 ]; then\n        echo \"Failed tests:\"\n        for test in \"${failed_tests[@]}\"; do\n            echo \"  - $(basename \"$test\")\"\n        done\n    fi\n\n    exit $failed\n}\n\nmain"
  }
]